@cluesmith/codev 2.0.0-rc.56 → 2.0.0-rc.57
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/dashboard/dist/assets/{index-xOaDIZ0l.js → index-bhDjF0Oa.js} +26 -27
- package/dashboard/dist/assets/index-bhDjF0Oa.js.map +1 -0
- package/dashboard/dist/index.html +1 -1
- package/dist/agent-farm/commands/send.d.ts.map +1 -1
- package/dist/agent-farm/commands/send.js +55 -17
- package/dist/agent-farm/commands/send.js.map +1 -1
- package/dist/agent-farm/servers/tower-server.js +202 -0
- package/dist/agent-farm/servers/tower-server.js.map +1 -1
- package/package.json +1 -1
- package/skeleton/protocols/bugfix/prompts/pr.md +2 -17
- package/skeleton/protocols/spider/prompts/plan.md +4 -68
- package/skeleton/protocols/spider/prompts/review.md +5 -23
- package/skeleton/protocols/spider/prompts/specify.md +4 -57
- package/dashboard/dist/assets/index-xOaDIZ0l.js.map +0 -1
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🏗️</text></svg>">
|
|
7
7
|
<title>Agent Farm Dashboard</title>
|
|
8
|
-
<script type="module" crossorigin src="./assets/index-
|
|
8
|
+
<script type="module" crossorigin src="./assets/index-bhDjF0Oa.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="./assets/index-BV7KQvFU.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"send.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/commands/send.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"send.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/commands/send.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAqS/C;;GAEG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAkE9D"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { readFileSync, writeFileSync, unlinkSync } from 'node:fs';
|
|
5
5
|
import { existsSync } from 'node:fs';
|
|
6
6
|
import { tmpdir } from 'node:os';
|
|
7
|
-
import { join } from 'node:path';
|
|
7
|
+
import { join, basename, dirname } from 'node:path';
|
|
8
8
|
import { randomUUID } from 'node:crypto';
|
|
9
9
|
import { logger, fatal } from '../utils/logger.js';
|
|
10
10
|
import { run } from '../utils/shell.js';
|
|
@@ -112,26 +112,64 @@ async function sendToBuilder(builderId, message, options) {
|
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
/**
|
|
115
|
-
*
|
|
115
|
+
* Detect project root from CWD by walking up to find .git or af-config.json.
|
|
116
|
+
* Builder worktrees are at .builders/<id>/ which is inside the project root.
|
|
116
117
|
*/
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
function detectProjectRoot() {
|
|
119
|
+
let dir = process.cwd();
|
|
120
|
+
// If inside .builders/<id>/, the project root is two levels up
|
|
121
|
+
const buildersMatch = dir.match(/^(.+?)\/\.builders\/[^/]+/);
|
|
122
|
+
if (buildersMatch)
|
|
123
|
+
return buildersMatch[1];
|
|
124
|
+
// Walk up looking for markers
|
|
125
|
+
for (let i = 0; i < 20; i++) {
|
|
126
|
+
if (existsSync(join(dir, 'af-config.json')) || existsSync(join(dir, '.git')))
|
|
127
|
+
return dir;
|
|
128
|
+
const parent = dirname(dir);
|
|
129
|
+
if (parent === dir)
|
|
130
|
+
break;
|
|
131
|
+
dir = parent;
|
|
121
132
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Find the architect tmux session name.
|
|
137
|
+
* Checks state.db first (legacy), then falls back to Tower naming convention.
|
|
138
|
+
*/
|
|
139
|
+
async function findArchitectTmuxSession() {
|
|
140
|
+
// Try legacy state.db first
|
|
141
|
+
const architect = getArchitect();
|
|
142
|
+
if (architect?.tmuxSession) {
|
|
143
|
+
try {
|
|
144
|
+
await run(`tmux has-session -t "${architect.tmuxSession}" 2>/dev/null`);
|
|
145
|
+
return architect.tmuxSession;
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// Session recorded but no longer exists — fall through
|
|
149
|
+
}
|
|
128
150
|
}
|
|
129
|
-
|
|
130
|
-
|
|
151
|
+
// Fallback: Tower creates architect sessions as "architect-<project-basename>"
|
|
152
|
+
const projectRoot = detectProjectRoot();
|
|
153
|
+
if (projectRoot) {
|
|
154
|
+
const candidateSession = `architect-${basename(projectRoot)}`;
|
|
155
|
+
try {
|
|
156
|
+
await run(`tmux has-session -t "${candidateSession}" 2>/dev/null`);
|
|
157
|
+
return candidateSession;
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// Session doesn't exist
|
|
161
|
+
}
|
|
131
162
|
}
|
|
163
|
+
throw new Error('Architect not running. Use "af status" to check.');
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Send a message to the architect (from a builder)
|
|
167
|
+
*/
|
|
168
|
+
async function sendToArchitect(fromBuilderId, message, options) {
|
|
169
|
+
const tmuxSession = await findArchitectTmuxSession();
|
|
132
170
|
// Optional: Send Ctrl+C first to interrupt any running process
|
|
133
171
|
if (options.interrupt) {
|
|
134
|
-
await run(`tmux send-keys -t "${
|
|
172
|
+
await run(`tmux send-keys -t "${tmuxSession}" C-c`);
|
|
135
173
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
136
174
|
}
|
|
137
175
|
// Load file content if specified
|
|
@@ -155,12 +193,12 @@ async function sendToArchitect(fromBuilderId, message, options) {
|
|
|
155
193
|
// Load into tmux buffer and paste
|
|
156
194
|
const bufferName = `builder-${fromBuilderId}`;
|
|
157
195
|
await run(`tmux load-buffer -b "${bufferName}" "${tempFile}"`);
|
|
158
|
-
await run(`tmux paste-buffer -b "${bufferName}" -t "${
|
|
196
|
+
await run(`tmux paste-buffer -b "${bufferName}" -t "${tmuxSession}"`);
|
|
159
197
|
// Clean up tmux buffer
|
|
160
198
|
await run(`tmux delete-buffer -b "${bufferName}"`).catch(() => { });
|
|
161
199
|
// Send Enter to submit (unless --no-enter)
|
|
162
200
|
if (!options.noEnter) {
|
|
163
|
-
await run(`tmux send-keys -t "${
|
|
201
|
+
await run(`tmux send-keys -t "${tmuxSession}" Enter`);
|
|
164
202
|
}
|
|
165
203
|
logger.debug(`Sent to architect from ${fromBuilderId}: ${message.substring(0, 50)}...`);
|
|
166
204
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"send.js","sourceRoot":"","sources":["../../../src/agent-farm/commands/send.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"send.js","sourceRoot":"","sources":["../../../src/agent-farm/commands/send.ts"],"names":[],"mappings":"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"}
|
|
@@ -242,6 +242,11 @@ function createTmuxSession(sessionName, command, args, cwd, cols, rows) {
|
|
|
242
242
|
log('WARN', `tmux new-session exited with code ${result.status} for "${sessionName}"`);
|
|
243
243
|
return false;
|
|
244
244
|
}
|
|
245
|
+
// Hide tmux status bar (dashboard has its own tabs), enable mouse, and
|
|
246
|
+
// use aggressive-resize so tmux sizes to the largest client (not smallest)
|
|
247
|
+
spawnSync('tmux', ['set-option', '-t', sessionName, 'status', 'off'], { stdio: 'ignore' });
|
|
248
|
+
spawnSync('tmux', ['set-option', '-t', sessionName, 'mouse', 'on'], { stdio: 'ignore' });
|
|
249
|
+
spawnSync('tmux', ['set-option', '-t', sessionName, 'aggressive-resize', 'on'], { stdio: 'ignore' });
|
|
245
250
|
return true;
|
|
246
251
|
}
|
|
247
252
|
catch (err) {
|
|
@@ -1736,6 +1741,27 @@ const server = http.createServer(async (req, res) => {
|
|
|
1736
1741
|
// Phase 4 (Spec 0090): Tower handles everything directly
|
|
1737
1742
|
const isApiCall = subPath.startsWith('api/') || subPath === 'api';
|
|
1738
1743
|
const isWsPath = subPath.startsWith('ws/') || subPath === 'ws';
|
|
1744
|
+
// GET /file?path=<relative-path> — Read project file by path (for StatusPanel project list)
|
|
1745
|
+
if (req.method === 'GET' && subPath === 'file' && url.searchParams.has('path')) {
|
|
1746
|
+
const relPath = url.searchParams.get('path');
|
|
1747
|
+
const fullPath = path.resolve(projectPath, relPath);
|
|
1748
|
+
// Security: ensure resolved path stays within project directory
|
|
1749
|
+
if (!fullPath.startsWith(projectPath + path.sep) && fullPath !== projectPath) {
|
|
1750
|
+
res.writeHead(403, { 'Content-Type': 'text/plain' });
|
|
1751
|
+
res.end('Forbidden');
|
|
1752
|
+
return;
|
|
1753
|
+
}
|
|
1754
|
+
try {
|
|
1755
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
1756
|
+
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
1757
|
+
res.end(content);
|
|
1758
|
+
}
|
|
1759
|
+
catch {
|
|
1760
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
1761
|
+
res.end('Not found');
|
|
1762
|
+
}
|
|
1763
|
+
return;
|
|
1764
|
+
}
|
|
1739
1765
|
// Serve React dashboard static files directly if:
|
|
1740
1766
|
// 1. Not an API call
|
|
1741
1767
|
// 2. Not a WebSocket path
|
|
@@ -2113,6 +2139,44 @@ const server = http.createServer(async (req, res) => {
|
|
|
2113
2139
|
res.end(JSON.stringify({ ok: true }));
|
|
2114
2140
|
return;
|
|
2115
2141
|
}
|
|
2142
|
+
// GET /api/files - Return project directory tree for file browser (Spec 0092)
|
|
2143
|
+
if (req.method === 'GET' && apiPath === 'files') {
|
|
2144
|
+
const maxDepth = parseInt(url.searchParams.get('depth') || '3', 10);
|
|
2145
|
+
const ignore = new Set(['.git', 'node_modules', '.builders', 'dist', '.agent-farm', '.next', '.cache', '__pycache__']);
|
|
2146
|
+
function readTree(dir, depth) {
|
|
2147
|
+
if (depth <= 0)
|
|
2148
|
+
return [];
|
|
2149
|
+
try {
|
|
2150
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
2151
|
+
return entries
|
|
2152
|
+
.filter(e => !e.name.startsWith('.') || e.name === '.env.example')
|
|
2153
|
+
.filter(e => !ignore.has(e.name))
|
|
2154
|
+
.sort((a, b) => {
|
|
2155
|
+
// Directories first, then alphabetical
|
|
2156
|
+
if (a.isDirectory() && !b.isDirectory())
|
|
2157
|
+
return -1;
|
|
2158
|
+
if (!a.isDirectory() && b.isDirectory())
|
|
2159
|
+
return 1;
|
|
2160
|
+
return a.name.localeCompare(b.name);
|
|
2161
|
+
})
|
|
2162
|
+
.map(e => {
|
|
2163
|
+
const fullPath = path.join(dir, e.name);
|
|
2164
|
+
const relativePath = path.relative(projectPath, fullPath);
|
|
2165
|
+
if (e.isDirectory()) {
|
|
2166
|
+
return { name: e.name, path: relativePath, type: 'directory', children: readTree(fullPath, depth - 1) };
|
|
2167
|
+
}
|
|
2168
|
+
return { name: e.name, path: relativePath, type: 'file' };
|
|
2169
|
+
});
|
|
2170
|
+
}
|
|
2171
|
+
catch {
|
|
2172
|
+
return [];
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
const tree = readTree(projectPath, maxDepth);
|
|
2176
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2177
|
+
res.end(JSON.stringify(tree));
|
|
2178
|
+
return;
|
|
2179
|
+
}
|
|
2116
2180
|
// GET /api/git/status - Return git status for file browser (Spec 0092)
|
|
2117
2181
|
if (req.method === 'GET' && apiPath === 'git/status') {
|
|
2118
2182
|
try {
|
|
@@ -2172,6 +2236,144 @@ const server = http.createServer(async (req, res) => {
|
|
|
2172
2236
|
res.end(JSON.stringify(recentFiles));
|
|
2173
2237
|
return;
|
|
2174
2238
|
}
|
|
2239
|
+
// GET /api/annotate/:tabId/* — Serve rich annotator template and sub-APIs
|
|
2240
|
+
const annotateMatch = apiPath.match(/^annotate\/([^/]+)(\/(.*))?$/);
|
|
2241
|
+
if (annotateMatch) {
|
|
2242
|
+
const tabId = annotateMatch[1];
|
|
2243
|
+
const subRoute = annotateMatch[3] || '';
|
|
2244
|
+
const entry = getProjectTerminalsEntry(projectPath);
|
|
2245
|
+
const tab = entry.fileTabs.get(tabId);
|
|
2246
|
+
if (!tab) {
|
|
2247
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
2248
|
+
res.end('File tab not found');
|
|
2249
|
+
return;
|
|
2250
|
+
}
|
|
2251
|
+
const filePath = tab.path;
|
|
2252
|
+
const ext = path.extname(filePath).slice(1).toLowerCase();
|
|
2253
|
+
const isImage = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg'].includes(ext);
|
|
2254
|
+
const isVideo = ['mp4', 'webm', 'mov'].includes(ext);
|
|
2255
|
+
const is3D = ['stl', '3mf'].includes(ext);
|
|
2256
|
+
const isMarkdown = ext === 'md';
|
|
2257
|
+
// Sub-route: GET /file — re-read file content from disk
|
|
2258
|
+
if (req.method === 'GET' && subRoute === 'file') {
|
|
2259
|
+
try {
|
|
2260
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
2261
|
+
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
2262
|
+
res.end(content);
|
|
2263
|
+
}
|
|
2264
|
+
catch (err) {
|
|
2265
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
2266
|
+
res.end(err.message);
|
|
2267
|
+
}
|
|
2268
|
+
return;
|
|
2269
|
+
}
|
|
2270
|
+
// Sub-route: POST /save — save file content
|
|
2271
|
+
if (req.method === 'POST' && subRoute === 'save') {
|
|
2272
|
+
try {
|
|
2273
|
+
const body = await new Promise((resolve) => {
|
|
2274
|
+
let data = '';
|
|
2275
|
+
req.on('data', (chunk) => data += chunk.toString());
|
|
2276
|
+
req.on('end', () => resolve(data));
|
|
2277
|
+
});
|
|
2278
|
+
const parsed = JSON.parse(body || '{}');
|
|
2279
|
+
const fileContent = parsed.content;
|
|
2280
|
+
if (typeof fileContent !== 'string') {
|
|
2281
|
+
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
2282
|
+
res.end('Missing content');
|
|
2283
|
+
return;
|
|
2284
|
+
}
|
|
2285
|
+
fs.writeFileSync(filePath, fileContent, 'utf-8');
|
|
2286
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2287
|
+
res.end(JSON.stringify({ ok: true }));
|
|
2288
|
+
}
|
|
2289
|
+
catch (err) {
|
|
2290
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
2291
|
+
res.end(err.message);
|
|
2292
|
+
}
|
|
2293
|
+
return;
|
|
2294
|
+
}
|
|
2295
|
+
// Sub-route: GET /api/mtime — file modification time
|
|
2296
|
+
if (req.method === 'GET' && subRoute === 'api/mtime') {
|
|
2297
|
+
try {
|
|
2298
|
+
const stat = fs.statSync(filePath);
|
|
2299
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2300
|
+
res.end(JSON.stringify({ mtime: stat.mtimeMs }));
|
|
2301
|
+
}
|
|
2302
|
+
catch (err) {
|
|
2303
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
2304
|
+
res.end(err.message);
|
|
2305
|
+
}
|
|
2306
|
+
return;
|
|
2307
|
+
}
|
|
2308
|
+
// Sub-route: GET /api/image, /api/video, /api/model — raw binary content
|
|
2309
|
+
if (req.method === 'GET' && (subRoute === 'api/image' || subRoute === 'api/video' || subRoute === 'api/model')) {
|
|
2310
|
+
try {
|
|
2311
|
+
const data = fs.readFileSync(filePath);
|
|
2312
|
+
const mimeType = getMimeTypeForFile(filePath);
|
|
2313
|
+
res.writeHead(200, {
|
|
2314
|
+
'Content-Type': mimeType,
|
|
2315
|
+
'Content-Length': data.length,
|
|
2316
|
+
'Cache-Control': 'no-cache',
|
|
2317
|
+
});
|
|
2318
|
+
res.end(data);
|
|
2319
|
+
}
|
|
2320
|
+
catch (err) {
|
|
2321
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
2322
|
+
res.end(err.message);
|
|
2323
|
+
}
|
|
2324
|
+
return;
|
|
2325
|
+
}
|
|
2326
|
+
// Default: serve the annotator HTML template
|
|
2327
|
+
if (req.method === 'GET' && (subRoute === '' || subRoute === undefined)) {
|
|
2328
|
+
try {
|
|
2329
|
+
const templateFile = is3D ? '3d-viewer.html' : 'open.html';
|
|
2330
|
+
const tplPath = path.resolve(__dirname, `../../../templates/${templateFile}`);
|
|
2331
|
+
let html = fs.readFileSync(tplPath, 'utf-8');
|
|
2332
|
+
const fileName = path.basename(filePath);
|
|
2333
|
+
const fileSize = fs.statSync(filePath).size;
|
|
2334
|
+
if (is3D) {
|
|
2335
|
+
html = html.replace(/\{\{FILE\}\}/g, fileName);
|
|
2336
|
+
html = html.replace(/\{\{FILE_PATH_JSON\}\}/g, JSON.stringify(filePath));
|
|
2337
|
+
html = html.replace(/\{\{FORMAT\}\}/g, ext);
|
|
2338
|
+
}
|
|
2339
|
+
else {
|
|
2340
|
+
html = html.replace(/\{\{FILE\}\}/g, fileName);
|
|
2341
|
+
html = html.replace(/\{\{FILE_PATH\}\}/g, filePath);
|
|
2342
|
+
html = html.replace(/\{\{BUILDER_ID\}\}/g, '');
|
|
2343
|
+
html = html.replace(/\{\{LANG\}\}/g, getLanguageForExt(ext));
|
|
2344
|
+
html = html.replace(/\{\{IS_MARKDOWN\}\}/g, String(isMarkdown));
|
|
2345
|
+
html = html.replace(/\{\{IS_IMAGE\}\}/g, String(isImage));
|
|
2346
|
+
html = html.replace(/\{\{IS_VIDEO\}\}/g, String(isVideo));
|
|
2347
|
+
html = html.replace(/\{\{FILE_SIZE\}\}/g, String(fileSize));
|
|
2348
|
+
// Inject initialization script (template loads content via fetch)
|
|
2349
|
+
let initScript;
|
|
2350
|
+
if (isImage) {
|
|
2351
|
+
initScript = `initImage(${fileSize});`;
|
|
2352
|
+
}
|
|
2353
|
+
else if (isVideo) {
|
|
2354
|
+
initScript = `initVideo(${fileSize});`;
|
|
2355
|
+
}
|
|
2356
|
+
else {
|
|
2357
|
+
initScript = `fetch('file').then(r=>r.text()).then(init);`;
|
|
2358
|
+
}
|
|
2359
|
+
html = html.replace('// FILE_CONTENT will be injected by the server', initScript);
|
|
2360
|
+
}
|
|
2361
|
+
// Handle ?line= query param for scroll-to-line
|
|
2362
|
+
const lineParam = url.searchParams.get('line');
|
|
2363
|
+
if (lineParam) {
|
|
2364
|
+
const scrollScript = `<script>window.addEventListener('load',()=>{setTimeout(()=>{const el=document.querySelector('[data-line="${lineParam}"]');if(el){el.scrollIntoView({block:'center'});el.classList.add('highlighted-line');}},200);})</script>`;
|
|
2365
|
+
html = html.replace('</body>', `${scrollScript}</body>`);
|
|
2366
|
+
}
|
|
2367
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
2368
|
+
res.end(html);
|
|
2369
|
+
}
|
|
2370
|
+
catch (err) {
|
|
2371
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
2372
|
+
res.end(`Failed to serve annotator: ${err.message}`);
|
|
2373
|
+
}
|
|
2374
|
+
return;
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2175
2377
|
// Unhandled API route
|
|
2176
2378
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
2177
2379
|
res.end(JSON.stringify({ error: 'API endpoint not found', path: apiPath }));
|