@aprovan/stitchery 0.1.0-dev.6bd527d
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/.turbo/turbo-build.log +36 -0
- package/LICENSE +373 -0
- package/dist/cli.cjs +1400 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +1389 -0
- package/dist/cli.js.map +1 -0
- package/dist/index-DNQY1UAP.d.cts +195 -0
- package/dist/index-DNQY1UAP.d.ts +195 -0
- package/dist/index.cjs +1337 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +1308 -0
- package/dist/index.js.map +1 -0
- package/dist/server/index.cjs +1327 -0
- package/dist/server/index.cjs.map +1 -0
- package/dist/server/index.d.cts +3 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.js +1304 -0
- package/dist/server/index.js.map +1 -0
- package/package.json +42 -0
- package/src/cli.ts +116 -0
- package/src/index.ts +16 -0
- package/src/prompts.ts +326 -0
- package/src/server/index.ts +365 -0
- package/src/server/local-packages.ts +91 -0
- package/src/server/routes.ts +122 -0
- package/src/server/services.ts +382 -0
- package/src/server/vfs-routes.ts +332 -0
- package/src/types.ts +59 -0
- package/tsconfig.json +13 -0
- package/tsup.config.ts +15 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readFile,
|
|
3
|
+
writeFile,
|
|
4
|
+
unlink,
|
|
5
|
+
readdir,
|
|
6
|
+
stat,
|
|
7
|
+
mkdir,
|
|
8
|
+
rm,
|
|
9
|
+
} from "node:fs/promises";
|
|
10
|
+
import { watch } from "node:fs";
|
|
11
|
+
import { dirname, resolve, sep } from "node:path";
|
|
12
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
13
|
+
|
|
14
|
+
export interface VFSContext {
|
|
15
|
+
rootDir: string;
|
|
16
|
+
usePaths: boolean;
|
|
17
|
+
log: (...args: unknown[]) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeRelPath(path: string): string {
|
|
21
|
+
const decoded = decodeURIComponent(path).replace(/\\/g, "/");
|
|
22
|
+
return decoded.replace(/^\/+|\/+$/g, "");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function resolvePath(rootDir: string, relPath: string): string {
|
|
26
|
+
const root = resolve(rootDir);
|
|
27
|
+
const full = resolve(root, relPath);
|
|
28
|
+
if (full !== root && !full.startsWith(`${root}${sep}`)) {
|
|
29
|
+
throw new Error("Invalid path");
|
|
30
|
+
}
|
|
31
|
+
return full;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function joinRelPath(base: string, name: string): string {
|
|
35
|
+
return base ? `${base}/${name}` : name;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function listAllFiles(
|
|
39
|
+
rootDir: string,
|
|
40
|
+
relPath: string,
|
|
41
|
+
): Promise<string[]> {
|
|
42
|
+
const targetPath = resolvePath(rootDir, relPath);
|
|
43
|
+
let entries: Awaited<ReturnType<typeof readdir>> = [];
|
|
44
|
+
try {
|
|
45
|
+
entries = await readdir(targetPath, { withFileTypes: true });
|
|
46
|
+
} catch {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const results: string[] = [];
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
const entryRelPath = joinRelPath(relPath, entry.name);
|
|
53
|
+
if (entry.isDirectory()) {
|
|
54
|
+
results.push(...(await listAllFiles(rootDir, entryRelPath)));
|
|
55
|
+
} else {
|
|
56
|
+
results.push(entryRelPath);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return results.sort((a, b) => a.localeCompare(b));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function ensureDir(filePath: string): Promise<void> {
|
|
64
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function sendJson(res: ServerResponse, status: number, body: unknown): void {
|
|
68
|
+
res.setHeader("Content-Type", "application/json");
|
|
69
|
+
res.writeHead(status);
|
|
70
|
+
res.end(JSON.stringify(body));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function handleVFS(
|
|
74
|
+
req: IncomingMessage,
|
|
75
|
+
res: ServerResponse,
|
|
76
|
+
ctx: VFSContext,
|
|
77
|
+
): boolean {
|
|
78
|
+
const url = req.url || "/";
|
|
79
|
+
const method = req.method || "GET";
|
|
80
|
+
|
|
81
|
+
if (!url.startsWith("/vfs")) return false;
|
|
82
|
+
|
|
83
|
+
// Handle config endpoint
|
|
84
|
+
if (url === "/vfs/config" && method === "GET") {
|
|
85
|
+
res.setHeader("Content-Type", "application/json");
|
|
86
|
+
res.writeHead(200);
|
|
87
|
+
res.end(JSON.stringify({ usePaths: ctx.usePaths }));
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const handleRequest = async () => {
|
|
92
|
+
const urlObj = new URL(url, "http://localhost");
|
|
93
|
+
const query = urlObj.searchParams;
|
|
94
|
+
const rawPath = urlObj.pathname.slice(4);
|
|
95
|
+
const relPath = normalizeRelPath(rawPath);
|
|
96
|
+
|
|
97
|
+
if (query.has("watch")) {
|
|
98
|
+
if (method !== "GET") {
|
|
99
|
+
res.writeHead(405);
|
|
100
|
+
res.end("Method not allowed");
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const watchPath = normalizeRelPath(query.get("watch") || "");
|
|
105
|
+
const fullWatchPath = resolvePath(ctx.rootDir, watchPath);
|
|
106
|
+
let watchStats;
|
|
107
|
+
try {
|
|
108
|
+
watchStats = await stat(fullWatchPath);
|
|
109
|
+
} catch {
|
|
110
|
+
res.writeHead(404);
|
|
111
|
+
res.end("Not found");
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
116
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
117
|
+
res.setHeader("Connection", "keep-alive");
|
|
118
|
+
res.writeHead(200);
|
|
119
|
+
|
|
120
|
+
const watcher = watch(
|
|
121
|
+
fullWatchPath,
|
|
122
|
+
{ recursive: watchStats.isDirectory() },
|
|
123
|
+
async (eventType, filename) => {
|
|
124
|
+
const eventPath = normalizeRelPath(
|
|
125
|
+
[watchPath, filename ? filename.toString() : ""]
|
|
126
|
+
.filter(Boolean)
|
|
127
|
+
.join("/"),
|
|
128
|
+
);
|
|
129
|
+
const fullEventPath = resolvePath(ctx.rootDir, eventPath);
|
|
130
|
+
|
|
131
|
+
let type: "create" | "update" | "delete" = "update";
|
|
132
|
+
if (eventType === "rename") {
|
|
133
|
+
try {
|
|
134
|
+
await stat(fullEventPath);
|
|
135
|
+
type = "create";
|
|
136
|
+
} catch {
|
|
137
|
+
type = "delete";
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
res.write("event: change\n");
|
|
142
|
+
res.write(
|
|
143
|
+
`data: ${JSON.stringify({
|
|
144
|
+
type,
|
|
145
|
+
path: eventPath,
|
|
146
|
+
mtime: new Date().toISOString(),
|
|
147
|
+
})}\n\n`,
|
|
148
|
+
);
|
|
149
|
+
},
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
req.on("close", () => watcher.close());
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (method === "HEAD" && !relPath) {
|
|
157
|
+
res.writeHead(200);
|
|
158
|
+
res.end();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (method === "GET" && !relPath && !query.toString()) {
|
|
163
|
+
const files = await listAllFiles(ctx.rootDir, "");
|
|
164
|
+
sendJson(res, 200, files);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (!relPath && method !== "GET" && method !== "HEAD") {
|
|
169
|
+
res.writeHead(400);
|
|
170
|
+
res.end("Invalid path");
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const targetPath = resolvePath(ctx.rootDir, relPath);
|
|
175
|
+
|
|
176
|
+
if (method === "GET" && query.get("stat") === "true") {
|
|
177
|
+
try {
|
|
178
|
+
const stats = await stat(targetPath);
|
|
179
|
+
sendJson(res, 200, {
|
|
180
|
+
size: stats.size,
|
|
181
|
+
mtime: stats.mtime.toISOString(),
|
|
182
|
+
isFile: stats.isFile(),
|
|
183
|
+
isDirectory: stats.isDirectory(),
|
|
184
|
+
});
|
|
185
|
+
} catch {
|
|
186
|
+
res.writeHead(404);
|
|
187
|
+
res.end("Not found");
|
|
188
|
+
}
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (method === "GET" && query.get("readdir") === "true") {
|
|
193
|
+
try {
|
|
194
|
+
const entries = await readdir(targetPath, { withFileTypes: true });
|
|
195
|
+
const mapped = entries
|
|
196
|
+
.map((entry) => ({
|
|
197
|
+
name: entry.name,
|
|
198
|
+
isDirectory: entry.isDirectory(),
|
|
199
|
+
}))
|
|
200
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
201
|
+
sendJson(res, 200, mapped);
|
|
202
|
+
} catch (err) {
|
|
203
|
+
const code = (err as NodeJS.ErrnoException).code;
|
|
204
|
+
if (code === "ENOTDIR") {
|
|
205
|
+
res.writeHead(409);
|
|
206
|
+
res.end("Not a directory");
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
res.writeHead(404);
|
|
210
|
+
res.end("Not found");
|
|
211
|
+
}
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (method === "POST" && query.get("mkdir") === "true") {
|
|
216
|
+
const recursive = query.get("recursive") === "true";
|
|
217
|
+
try {
|
|
218
|
+
await mkdir(targetPath, { recursive });
|
|
219
|
+
res.writeHead(200);
|
|
220
|
+
res.end("ok");
|
|
221
|
+
} catch {
|
|
222
|
+
res.writeHead(500);
|
|
223
|
+
res.end("Mkdir failed");
|
|
224
|
+
}
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
switch (method) {
|
|
229
|
+
case "GET": {
|
|
230
|
+
try {
|
|
231
|
+
const content = await readFile(targetPath, "utf-8");
|
|
232
|
+
res.setHeader("Content-Type", "text/plain");
|
|
233
|
+
res.writeHead(200);
|
|
234
|
+
res.end(content);
|
|
235
|
+
} catch {
|
|
236
|
+
res.writeHead(404);
|
|
237
|
+
res.end("Not found");
|
|
238
|
+
}
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
case "PUT": {
|
|
242
|
+
let body = "";
|
|
243
|
+
req.on("data", (chunk) => (body += chunk));
|
|
244
|
+
req.on("end", async () => {
|
|
245
|
+
try {
|
|
246
|
+
await ensureDir(targetPath);
|
|
247
|
+
await writeFile(targetPath, body, "utf-8");
|
|
248
|
+
res.writeHead(200);
|
|
249
|
+
res.end("ok");
|
|
250
|
+
} catch (err) {
|
|
251
|
+
ctx.log("VFS PUT error:", err);
|
|
252
|
+
res.writeHead(500);
|
|
253
|
+
res.end("Write failed");
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
case "DELETE": {
|
|
259
|
+
const recursive = query.get("recursive") === "true";
|
|
260
|
+
let stats;
|
|
261
|
+
try {
|
|
262
|
+
stats = await stat(targetPath);
|
|
263
|
+
} catch {
|
|
264
|
+
res.writeHead(404);
|
|
265
|
+
res.end("Not found");
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (stats.isDirectory()) {
|
|
270
|
+
if (!recursive) {
|
|
271
|
+
try {
|
|
272
|
+
const entries = await readdir(targetPath);
|
|
273
|
+
if (entries.length > 0) {
|
|
274
|
+
res.writeHead(409);
|
|
275
|
+
res.end("Directory not empty");
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
await rm(targetPath, { recursive: false });
|
|
279
|
+
res.writeHead(200);
|
|
280
|
+
res.end("ok");
|
|
281
|
+
} catch {
|
|
282
|
+
res.writeHead(500);
|
|
283
|
+
res.end("Delete failed");
|
|
284
|
+
}
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
try {
|
|
288
|
+
await rm(targetPath, { recursive: true });
|
|
289
|
+
res.writeHead(200);
|
|
290
|
+
res.end("ok");
|
|
291
|
+
} catch {
|
|
292
|
+
res.writeHead(500);
|
|
293
|
+
res.end("Delete failed");
|
|
294
|
+
}
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
await unlink(targetPath);
|
|
300
|
+
res.writeHead(200);
|
|
301
|
+
res.end("ok");
|
|
302
|
+
} catch {
|
|
303
|
+
res.writeHead(404);
|
|
304
|
+
res.end("Not found");
|
|
305
|
+
}
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
case "HEAD": {
|
|
309
|
+
try {
|
|
310
|
+
await stat(targetPath);
|
|
311
|
+
res.writeHead(200);
|
|
312
|
+
res.end();
|
|
313
|
+
} catch {
|
|
314
|
+
res.writeHead(404);
|
|
315
|
+
res.end();
|
|
316
|
+
}
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
default:
|
|
320
|
+
res.writeHead(405);
|
|
321
|
+
res.end("Method not allowed");
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
handleRequest().catch((err) => {
|
|
326
|
+
ctx.log("VFS error:", err);
|
|
327
|
+
res.writeHead(500);
|
|
328
|
+
res.end("Internal error");
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
return true;
|
|
332
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UTCP service configuration
|
|
3
|
+
* Used to register services via UTCP protocol
|
|
4
|
+
*/
|
|
5
|
+
export interface UtcpConfig {
|
|
6
|
+
/** Working directory for UTCP operations */
|
|
7
|
+
cwd?: string;
|
|
8
|
+
/** Manual call templates (service definitions) */
|
|
9
|
+
manual_call_templates?: Array<{
|
|
10
|
+
name: string;
|
|
11
|
+
call_template_type: string;
|
|
12
|
+
url?: string;
|
|
13
|
+
http_method?: string;
|
|
14
|
+
[key: string]: unknown;
|
|
15
|
+
}>;
|
|
16
|
+
/** Additional UTCP options */
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ServerConfig {
|
|
21
|
+
port: number;
|
|
22
|
+
host: string;
|
|
23
|
+
copilotProxyUrl: string;
|
|
24
|
+
localPackages: Record<string, string>;
|
|
25
|
+
mcpServers: McpServerConfig[];
|
|
26
|
+
/** UTCP configuration for registering services */
|
|
27
|
+
utcp?: UtcpConfig;
|
|
28
|
+
/** Directory for virtual file system storage */
|
|
29
|
+
vfsDir?: string;
|
|
30
|
+
/** Use file paths from code blocks instead of UUIDs */
|
|
31
|
+
vfsUsePaths?: boolean;
|
|
32
|
+
verbose: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface McpServerConfig {
|
|
36
|
+
name: string;
|
|
37
|
+
command: string;
|
|
38
|
+
args: string[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ChatRequest {
|
|
42
|
+
messages: UIMessage[];
|
|
43
|
+
metadata?: {
|
|
44
|
+
patchwork?: {
|
|
45
|
+
compilers?: string[];
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface EditRequest {
|
|
51
|
+
code: string;
|
|
52
|
+
prompt: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface UIMessage {
|
|
56
|
+
role: string;
|
|
57
|
+
content: string;
|
|
58
|
+
parts?: Array<{ type: string; text: string }>;
|
|
59
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "./dist",
|
|
5
|
+
"rootDir": "./src",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"declarationMap": true,
|
|
8
|
+
"module": "ESNext",
|
|
9
|
+
"moduleResolution": "bundler"
|
|
10
|
+
},
|
|
11
|
+
"include": ["src/**/*"],
|
|
12
|
+
"exclude": ["node_modules", "dist"]
|
|
13
|
+
}
|
package/tsup.config.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineConfig } from "tsup";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
entry: {
|
|
5
|
+
index: "src/index.ts",
|
|
6
|
+
cli: "src/cli.ts",
|
|
7
|
+
"server/index": "src/server/index.ts",
|
|
8
|
+
},
|
|
9
|
+
format: ["esm", "cjs"],
|
|
10
|
+
dts: true,
|
|
11
|
+
clean: true,
|
|
12
|
+
sourcemap: true,
|
|
13
|
+
splitting: false,
|
|
14
|
+
shims: true,
|
|
15
|
+
});
|