stashes 0.1.7 → 0.1.8
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/cli.js +161 -81
- package/dist/mcp.js +161 -81
- package/dist/web/assets/index-8mMmnYX5.js +62 -0
- package/dist/web/assets/index-BEubPa-l.css +1 -0
- package/dist/web/assets/index-BzwYXF3-.js +62 -0
- package/dist/web/assets/index-DBsH8rVY.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -37,6 +37,7 @@ import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
|
|
|
37
37
|
// ../shared/dist/constants/index.js
|
|
38
38
|
var STASHES_PORT = 4000;
|
|
39
39
|
var DEFAULT_STASH_COUNT = 3;
|
|
40
|
+
var APP_PROXY_PORT = STASHES_PORT + 1;
|
|
40
41
|
var STASH_PORT_START = 4010;
|
|
41
42
|
var DEFAULT_DIRECTIVES = [
|
|
42
43
|
"Minimal and clean \u2014 reduce visual noise, emphasize whitespace, limit to 2-3 colors, typography-driven hierarchy",
|
|
@@ -410,6 +411,9 @@ class PersistenceService {
|
|
|
410
411
|
const filePath = join4(this.basePath, "projects", projectId, "stashes.json");
|
|
411
412
|
return readJson(filePath, []);
|
|
412
413
|
}
|
|
414
|
+
getStash(projectId, stashId) {
|
|
415
|
+
return this.listStashes(projectId).find((s) => s.id === stashId);
|
|
416
|
+
}
|
|
413
417
|
saveStash(stash) {
|
|
414
418
|
const stashes = [...this.listStashes(stash.projectId)];
|
|
415
419
|
const index = stashes.findIndex((s) => s.id === stash.id);
|
|
@@ -973,7 +977,7 @@ class StashService {
|
|
|
973
977
|
});
|
|
974
978
|
}
|
|
975
979
|
}
|
|
976
|
-
async chat(projectId, message) {
|
|
980
|
+
async chat(projectId, message, referenceStashIds) {
|
|
977
981
|
const component = this.selectedComponent;
|
|
978
982
|
let sourceCode = "";
|
|
979
983
|
const filePath = component?.filePath || "";
|
|
@@ -983,8 +987,18 @@ class StashService {
|
|
|
983
987
|
sourceCode = readFileSync4(sourceFile, "utf-8");
|
|
984
988
|
}
|
|
985
989
|
}
|
|
990
|
+
let stashContext = "";
|
|
991
|
+
if (referenceStashIds?.length) {
|
|
992
|
+
const refs = referenceStashIds.map((id) => this.persistence.getStash(projectId, id)).filter(Boolean).map((s) => `- "${s.prompt}"${s.componentPath ? ` (${s.componentPath})` : ""}`);
|
|
993
|
+
if (refs.length) {
|
|
994
|
+
stashContext = `
|
|
995
|
+
Referenced stashes:
|
|
996
|
+
${refs.join(`
|
|
997
|
+
`)}`;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
986
1000
|
const chatPrompt = [
|
|
987
|
-
"The user is asking about
|
|
1001
|
+
"The user is asking about their UI project. Answer concisely.",
|
|
988
1002
|
"Do NOT modify any files.",
|
|
989
1003
|
"",
|
|
990
1004
|
component ? `Component: ${component.name}` : "",
|
|
@@ -994,6 +1008,7 @@ Source:
|
|
|
994
1008
|
\`\`\`
|
|
995
1009
|
${sourceCode.substring(0, 3000)}
|
|
996
1010
|
\`\`\`` : "",
|
|
1011
|
+
stashContext,
|
|
997
1012
|
"",
|
|
998
1013
|
`User question: ${message}`
|
|
999
1014
|
].filter(Boolean).join(`
|
|
@@ -1046,18 +1061,23 @@ ${sourceCode.substring(0, 3000)}
|
|
|
1046
1061
|
break;
|
|
1047
1062
|
}
|
|
1048
1063
|
}
|
|
1049
|
-
async generate(projectId, prompt, stashCount = DEFAULT_STASH_COUNT) {
|
|
1050
|
-
|
|
1051
|
-
|
|
1064
|
+
async generate(projectId, prompt, stashCount = DEFAULT_STASH_COUNT, referenceStashIds) {
|
|
1065
|
+
let enrichedPrompt = prompt;
|
|
1066
|
+
if (referenceStashIds?.length) {
|
|
1067
|
+
const refDescriptions = referenceStashIds.map((id) => this.persistence.getStash(projectId, id)).filter(Boolean).map((s) => `- "${s.prompt}"${s.componentPath ? ` (${s.componentPath})` : ""}`);
|
|
1068
|
+
if (refDescriptions.length) {
|
|
1069
|
+
enrichedPrompt = `${prompt}
|
|
1070
|
+
|
|
1071
|
+
## Reference Stashes (use as inspiration)
|
|
1072
|
+
${refDescriptions.join(`
|
|
1073
|
+
`)}`;
|
|
1074
|
+
}
|
|
1052
1075
|
}
|
|
1053
1076
|
await generate({
|
|
1054
1077
|
projectPath: this.projectPath,
|
|
1055
1078
|
projectId,
|
|
1056
|
-
prompt,
|
|
1057
|
-
component: {
|
|
1058
|
-
filePath: this.selectedComponent.filePath,
|
|
1059
|
-
exportName: this.selectedComponent.name
|
|
1060
|
-
},
|
|
1079
|
+
prompt: enrichedPrompt,
|
|
1080
|
+
component: this.selectedComponent ? { filePath: this.selectedComponent.filePath, exportName: this.selectedComponent.name } : undefined,
|
|
1061
1081
|
count: stashCount,
|
|
1062
1082
|
onProgress: (event) => this.progressToBroadcast(event)
|
|
1063
1083
|
});
|
|
@@ -1174,7 +1194,7 @@ function broadcast(event) {
|
|
|
1174
1194
|
function getPersistenceFromWs() {
|
|
1175
1195
|
return persistence;
|
|
1176
1196
|
}
|
|
1177
|
-
function createWebSocketHandler(projectPath, userDevPort) {
|
|
1197
|
+
function createWebSocketHandler(projectPath, userDevPort, appProxyPort) {
|
|
1178
1198
|
worktreeManager = new WorktreeManager(projectPath);
|
|
1179
1199
|
persistence = new PersistenceService(projectPath);
|
|
1180
1200
|
stashService = new StashService(projectPath, worktreeManager, persistence, broadcast);
|
|
@@ -1182,7 +1202,7 @@ function createWebSocketHandler(projectPath, userDevPort) {
|
|
|
1182
1202
|
open(ws) {
|
|
1183
1203
|
clients.add(ws);
|
|
1184
1204
|
logger.info("ws", "client connected", { total: clients.size });
|
|
1185
|
-
ws.send(JSON.stringify({ type: "server_ready", port: userDevPort }));
|
|
1205
|
+
ws.send(JSON.stringify({ type: "server_ready", port: userDevPort, appProxyPort }));
|
|
1186
1206
|
},
|
|
1187
1207
|
async message(ws, message) {
|
|
1188
1208
|
const raw = typeof message === "string" ? message : new TextDecoder().decode(message);
|
|
@@ -1209,7 +1229,7 @@ function createWebSocketHandler(projectPath, userDevPort) {
|
|
|
1209
1229
|
type: "text",
|
|
1210
1230
|
createdAt: new Date().toISOString()
|
|
1211
1231
|
});
|
|
1212
|
-
await stashService.chat(event.projectId, event.message);
|
|
1232
|
+
await stashService.chat(event.projectId, event.message, event.referenceStashIds);
|
|
1213
1233
|
break;
|
|
1214
1234
|
case "generate":
|
|
1215
1235
|
persistence.saveChatMessage(event.projectId, {
|
|
@@ -1219,7 +1239,7 @@ function createWebSocketHandler(projectPath, userDevPort) {
|
|
|
1219
1239
|
type: "text",
|
|
1220
1240
|
createdAt: new Date().toISOString()
|
|
1221
1241
|
});
|
|
1222
|
-
await stashService.generate(event.projectId, event.prompt, event.stashCount);
|
|
1242
|
+
await stashService.generate(event.projectId, event.prompt, event.stashCount, event.referenceStashIds);
|
|
1223
1243
|
break;
|
|
1224
1244
|
case "vary":
|
|
1225
1245
|
await stashService.vary(event.sourceStashId, event.prompt);
|
|
@@ -1247,6 +1267,111 @@ function createWebSocketHandler(projectPath, userDevPort) {
|
|
|
1247
1267
|
};
|
|
1248
1268
|
}
|
|
1249
1269
|
|
|
1270
|
+
// ../server/dist/services/app-proxy.js
|
|
1271
|
+
function startAppProxy(userDevPort, proxyPort, injectOverlay) {
|
|
1272
|
+
const server = Bun.serve({
|
|
1273
|
+
port: proxyPort,
|
|
1274
|
+
async fetch(req, server2) {
|
|
1275
|
+
if (req.headers.get("upgrade")?.toLowerCase() === "websocket") {
|
|
1276
|
+
const url2 = new URL(req.url);
|
|
1277
|
+
const success = server2.upgrade(req, {
|
|
1278
|
+
data: {
|
|
1279
|
+
path: url2.pathname + url2.search,
|
|
1280
|
+
upstream: null,
|
|
1281
|
+
ready: false,
|
|
1282
|
+
buffer: []
|
|
1283
|
+
}
|
|
1284
|
+
});
|
|
1285
|
+
return success ? undefined : new Response("WebSocket upgrade failed", { status: 400 });
|
|
1286
|
+
}
|
|
1287
|
+
const url = new URL(req.url);
|
|
1288
|
+
const targetUrl = `http://localhost:${userDevPort}${url.pathname}${url.search}`;
|
|
1289
|
+
try {
|
|
1290
|
+
const headers = new Headers;
|
|
1291
|
+
for (const [key, value] of req.headers.entries()) {
|
|
1292
|
+
if (!["host", "accept-encoding"].includes(key.toLowerCase())) {
|
|
1293
|
+
headers.set(key, value);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
headers.set("host", `localhost:${userDevPort}`);
|
|
1297
|
+
const response = await fetch(targetUrl, {
|
|
1298
|
+
method: req.method,
|
|
1299
|
+
headers,
|
|
1300
|
+
body: req.method !== "GET" && req.method !== "HEAD" ? await req.arrayBuffer() : undefined,
|
|
1301
|
+
redirect: "manual"
|
|
1302
|
+
});
|
|
1303
|
+
const contentType = response.headers.get("content-type") || "";
|
|
1304
|
+
if (contentType.includes("text/html")) {
|
|
1305
|
+
const html = await response.text();
|
|
1306
|
+
const injectedHtml = injectOverlay(html);
|
|
1307
|
+
const respHeaders2 = new Headers(response.headers);
|
|
1308
|
+
respHeaders2.delete("content-encoding");
|
|
1309
|
+
respHeaders2.delete("content-length");
|
|
1310
|
+
return new Response(injectedHtml, {
|
|
1311
|
+
status: response.status,
|
|
1312
|
+
headers: respHeaders2
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
const respHeaders = new Headers(response.headers);
|
|
1316
|
+
respHeaders.delete("content-encoding");
|
|
1317
|
+
return new Response(response.body, {
|
|
1318
|
+
status: response.status,
|
|
1319
|
+
headers: respHeaders
|
|
1320
|
+
});
|
|
1321
|
+
} catch (err) {
|
|
1322
|
+
return new Response(JSON.stringify({ error: "Proxy failed", detail: String(err) }), {
|
|
1323
|
+
status: 502,
|
|
1324
|
+
headers: { "content-type": "application/json" }
|
|
1325
|
+
});
|
|
1326
|
+
}
|
|
1327
|
+
},
|
|
1328
|
+
websocket: {
|
|
1329
|
+
open(ws) {
|
|
1330
|
+
const { data } = ws;
|
|
1331
|
+
const upstream = new WebSocket(`ws://localhost:${userDevPort}${data.path}`);
|
|
1332
|
+
upstream.addEventListener("open", () => {
|
|
1333
|
+
data.upstream = upstream;
|
|
1334
|
+
data.ready = true;
|
|
1335
|
+
for (const msg of data.buffer) {
|
|
1336
|
+
upstream.send(msg);
|
|
1337
|
+
}
|
|
1338
|
+
data.buffer = [];
|
|
1339
|
+
});
|
|
1340
|
+
upstream.addEventListener("message", (event) => {
|
|
1341
|
+
try {
|
|
1342
|
+
ws.sendText(typeof event.data === "string" ? event.data : String(event.data));
|
|
1343
|
+
} catch {}
|
|
1344
|
+
});
|
|
1345
|
+
upstream.addEventListener("close", () => {
|
|
1346
|
+
try {
|
|
1347
|
+
ws.close();
|
|
1348
|
+
} catch {}
|
|
1349
|
+
});
|
|
1350
|
+
upstream.addEventListener("error", () => {
|
|
1351
|
+
try {
|
|
1352
|
+
ws.close();
|
|
1353
|
+
} catch {}
|
|
1354
|
+
});
|
|
1355
|
+
},
|
|
1356
|
+
message(ws, msg) {
|
|
1357
|
+
const { data } = ws;
|
|
1358
|
+
if (data.ready && data.upstream) {
|
|
1359
|
+
data.upstream.send(typeof msg === "string" ? msg : new Uint8Array(msg));
|
|
1360
|
+
} else {
|
|
1361
|
+
data.buffer.push(typeof msg === "string" ? msg : new Uint8Array(msg).buffer);
|
|
1362
|
+
}
|
|
1363
|
+
},
|
|
1364
|
+
close(ws) {
|
|
1365
|
+
try {
|
|
1366
|
+
ws.data.upstream?.close();
|
|
1367
|
+
} catch {}
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
});
|
|
1371
|
+
logger.info("proxy", `App proxy at http://localhost:${proxyPort} \u2192 http://localhost:${userDevPort}`);
|
|
1372
|
+
return server;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1250
1375
|
// ../server/dist/index.js
|
|
1251
1376
|
var serverState = {
|
|
1252
1377
|
projectPath: "",
|
|
@@ -1258,56 +1383,6 @@ function getPersistence() {
|
|
|
1258
1383
|
var app2 = new Hono2;
|
|
1259
1384
|
app2.use("/*", cors());
|
|
1260
1385
|
app2.route("/api", apiRoutes);
|
|
1261
|
-
async function proxyToUserApp(c, targetPath, injectOverlay = false) {
|
|
1262
|
-
const userDevPort = serverState.userDevPort;
|
|
1263
|
-
const url = new URL(c.req.url);
|
|
1264
|
-
const targetUrl = `http://localhost:${userDevPort}${targetPath}${url.search}`;
|
|
1265
|
-
try {
|
|
1266
|
-
const headers = new Headers;
|
|
1267
|
-
for (const [key, value] of Object.entries(c.req.header())) {
|
|
1268
|
-
if (!["host", "accept-encoding"].includes(key.toLowerCase()) && value) {
|
|
1269
|
-
headers.set(key, value);
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
headers.set("host", `localhost:${userDevPort}`);
|
|
1273
|
-
const response = await fetch(targetUrl, {
|
|
1274
|
-
method: c.req.method,
|
|
1275
|
-
headers,
|
|
1276
|
-
body: c.req.method !== "GET" && c.req.method !== "HEAD" ? await c.req.arrayBuffer() : undefined,
|
|
1277
|
-
redirect: "manual"
|
|
1278
|
-
});
|
|
1279
|
-
const contentType = response.headers.get("content-type") || "";
|
|
1280
|
-
if (injectOverlay && contentType.includes("text/html")) {
|
|
1281
|
-
const html = await response.text();
|
|
1282
|
-
const injectedHtml = injectOverlayScript(html);
|
|
1283
|
-
const respHeaders2 = new Headers(response.headers);
|
|
1284
|
-
respHeaders2.delete("content-encoding");
|
|
1285
|
-
respHeaders2.delete("content-length");
|
|
1286
|
-
return new Response(injectedHtml, {
|
|
1287
|
-
status: response.status,
|
|
1288
|
-
headers: respHeaders2
|
|
1289
|
-
});
|
|
1290
|
-
}
|
|
1291
|
-
const respHeaders = new Headers(response.headers);
|
|
1292
|
-
respHeaders.delete("content-encoding");
|
|
1293
|
-
return new Response(response.body, {
|
|
1294
|
-
status: response.status,
|
|
1295
|
-
headers: respHeaders
|
|
1296
|
-
});
|
|
1297
|
-
} catch (err) {
|
|
1298
|
-
return new Response(JSON.stringify({ error: "Proxy failed", detail: String(err) }), {
|
|
1299
|
-
status: 502,
|
|
1300
|
-
headers: { "content-type": "application/json" }
|
|
1301
|
-
});
|
|
1302
|
-
}
|
|
1303
|
-
}
|
|
1304
|
-
app2.all("/app/*", (c) => {
|
|
1305
|
-
const path = c.req.path.replace(/^\/app/, "") || "/";
|
|
1306
|
-
return proxyToUserApp(c, path, true);
|
|
1307
|
-
});
|
|
1308
|
-
app2.all("/_next/*", (c) => proxyToUserApp(c, c.req.path));
|
|
1309
|
-
app2.all("/__nextjs*", (c) => proxyToUserApp(c, c.req.path));
|
|
1310
|
-
app2.all("/__next*", (c) => proxyToUserApp(c, c.req.path));
|
|
1311
1386
|
app2.get("/*", async (c) => {
|
|
1312
1387
|
const path = c.req.path;
|
|
1313
1388
|
const selfDir = dirname2(fileURLToPath(import.meta.url));
|
|
@@ -1350,7 +1425,9 @@ app2.get("/*", async (c) => {
|
|
|
1350
1425
|
function startServer(projectPath, userDevPort, port = STASHES_PORT) {
|
|
1351
1426
|
serverState = { projectPath, userDevPort };
|
|
1352
1427
|
initLogFile(projectPath);
|
|
1353
|
-
const
|
|
1428
|
+
const appProxyPort = port + 1;
|
|
1429
|
+
startAppProxy(userDevPort, appProxyPort, injectOverlayScript);
|
|
1430
|
+
const wsHandler = createWebSocketHandler(projectPath, userDevPort, appProxyPort);
|
|
1354
1431
|
const server = Bun.serve({
|
|
1355
1432
|
port,
|
|
1356
1433
|
fetch(req, server2) {
|
|
@@ -1515,22 +1592,25 @@ function injectOverlayScript(html) {
|
|
|
1515
1592
|
}
|
|
1516
1593
|
});
|
|
1517
1594
|
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1595
|
+
// Report current URL to parent for status bar display
|
|
1596
|
+
function reportUrl() {
|
|
1597
|
+
window.parent.postMessage({
|
|
1598
|
+
type: 'stashes:url_change',
|
|
1599
|
+
url: window.location.pathname + window.location.search + window.location.hash
|
|
1600
|
+
}, '*');
|
|
1601
|
+
}
|
|
1602
|
+
reportUrl();
|
|
1603
|
+
var origPush = history.pushState;
|
|
1604
|
+
history.pushState = function() {
|
|
1605
|
+
origPush.apply(this, arguments);
|
|
1606
|
+
setTimeout(reportUrl, 0);
|
|
1607
|
+
};
|
|
1608
|
+
var origReplace = history.replaceState;
|
|
1609
|
+
history.replaceState = function() {
|
|
1610
|
+
origReplace.apply(this, arguments);
|
|
1611
|
+
setTimeout(reportUrl, 0);
|
|
1612
|
+
};
|
|
1613
|
+
window.addEventListener('popstate', reportUrl);
|
|
1534
1614
|
|
|
1535
1615
|
document.addEventListener('mousemove', onMouseMove, { passive: true });
|
|
1536
1616
|
document.addEventListener('click', onClick, true);
|
package/dist/mcp.js
CHANGED
|
@@ -33,6 +33,7 @@ import simpleGit2 from "simple-git";
|
|
|
33
33
|
// ../shared/dist/constants/index.js
|
|
34
34
|
var STASHES_PORT = 4000;
|
|
35
35
|
var DEFAULT_STASH_COUNT = 3;
|
|
36
|
+
var APP_PROXY_PORT = STASHES_PORT + 1;
|
|
36
37
|
var STASH_PORT_START = 4010;
|
|
37
38
|
var DEFAULT_DIRECTIVES = [
|
|
38
39
|
"Minimal and clean \u2014 reduce visual noise, emphasize whitespace, limit to 2-3 colors, typography-driven hierarchy",
|
|
@@ -346,6 +347,9 @@ class PersistenceService {
|
|
|
346
347
|
const filePath = join3(this.basePath, "projects", projectId, "stashes.json");
|
|
347
348
|
return readJson(filePath, []);
|
|
348
349
|
}
|
|
350
|
+
getStash(projectId, stashId) {
|
|
351
|
+
return this.listStashes(projectId).find((s) => s.id === stashId);
|
|
352
|
+
}
|
|
349
353
|
saveStash(stash) {
|
|
350
354
|
const stashes = [...this.listStashes(stash.projectId)];
|
|
351
355
|
const index = stashes.findIndex((s) => s.id === stash.id);
|
|
@@ -1103,7 +1107,7 @@ class StashService {
|
|
|
1103
1107
|
});
|
|
1104
1108
|
}
|
|
1105
1109
|
}
|
|
1106
|
-
async chat(projectId, message) {
|
|
1110
|
+
async chat(projectId, message, referenceStashIds) {
|
|
1107
1111
|
const component = this.selectedComponent;
|
|
1108
1112
|
let sourceCode = "";
|
|
1109
1113
|
const filePath = component?.filePath || "";
|
|
@@ -1113,8 +1117,18 @@ class StashService {
|
|
|
1113
1117
|
sourceCode = readFileSync4(sourceFile, "utf-8");
|
|
1114
1118
|
}
|
|
1115
1119
|
}
|
|
1120
|
+
let stashContext = "";
|
|
1121
|
+
if (referenceStashIds?.length) {
|
|
1122
|
+
const refs = referenceStashIds.map((id) => this.persistence.getStash(projectId, id)).filter(Boolean).map((s) => `- "${s.prompt}"${s.componentPath ? ` (${s.componentPath})` : ""}`);
|
|
1123
|
+
if (refs.length) {
|
|
1124
|
+
stashContext = `
|
|
1125
|
+
Referenced stashes:
|
|
1126
|
+
${refs.join(`
|
|
1127
|
+
`)}`;
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1116
1130
|
const chatPrompt = [
|
|
1117
|
-
"The user is asking about
|
|
1131
|
+
"The user is asking about their UI project. Answer concisely.",
|
|
1118
1132
|
"Do NOT modify any files.",
|
|
1119
1133
|
"",
|
|
1120
1134
|
component ? `Component: ${component.name}` : "",
|
|
@@ -1124,6 +1138,7 @@ Source:
|
|
|
1124
1138
|
\`\`\`
|
|
1125
1139
|
${sourceCode.substring(0, 3000)}
|
|
1126
1140
|
\`\`\`` : "",
|
|
1141
|
+
stashContext,
|
|
1127
1142
|
"",
|
|
1128
1143
|
`User question: ${message}`
|
|
1129
1144
|
].filter(Boolean).join(`
|
|
@@ -1176,18 +1191,23 @@ ${sourceCode.substring(0, 3000)}
|
|
|
1176
1191
|
break;
|
|
1177
1192
|
}
|
|
1178
1193
|
}
|
|
1179
|
-
async generate(projectId, prompt, stashCount = DEFAULT_STASH_COUNT) {
|
|
1180
|
-
|
|
1181
|
-
|
|
1194
|
+
async generate(projectId, prompt, stashCount = DEFAULT_STASH_COUNT, referenceStashIds) {
|
|
1195
|
+
let enrichedPrompt = prompt;
|
|
1196
|
+
if (referenceStashIds?.length) {
|
|
1197
|
+
const refDescriptions = referenceStashIds.map((id) => this.persistence.getStash(projectId, id)).filter(Boolean).map((s) => `- "${s.prompt}"${s.componentPath ? ` (${s.componentPath})` : ""}`);
|
|
1198
|
+
if (refDescriptions.length) {
|
|
1199
|
+
enrichedPrompt = `${prompt}
|
|
1200
|
+
|
|
1201
|
+
## Reference Stashes (use as inspiration)
|
|
1202
|
+
${refDescriptions.join(`
|
|
1203
|
+
`)}`;
|
|
1204
|
+
}
|
|
1182
1205
|
}
|
|
1183
1206
|
await generate({
|
|
1184
1207
|
projectPath: this.projectPath,
|
|
1185
1208
|
projectId,
|
|
1186
|
-
prompt,
|
|
1187
|
-
component: {
|
|
1188
|
-
filePath: this.selectedComponent.filePath,
|
|
1189
|
-
exportName: this.selectedComponent.name
|
|
1190
|
-
},
|
|
1209
|
+
prompt: enrichedPrompt,
|
|
1210
|
+
component: this.selectedComponent ? { filePath: this.selectedComponent.filePath, exportName: this.selectedComponent.name } : undefined,
|
|
1191
1211
|
count: stashCount,
|
|
1192
1212
|
onProgress: (event) => this.progressToBroadcast(event)
|
|
1193
1213
|
});
|
|
@@ -1304,7 +1324,7 @@ function broadcast(event) {
|
|
|
1304
1324
|
function getPersistenceFromWs() {
|
|
1305
1325
|
return persistence;
|
|
1306
1326
|
}
|
|
1307
|
-
function createWebSocketHandler(projectPath, userDevPort) {
|
|
1327
|
+
function createWebSocketHandler(projectPath, userDevPort, appProxyPort) {
|
|
1308
1328
|
worktreeManager = new WorktreeManager(projectPath);
|
|
1309
1329
|
persistence = new PersistenceService(projectPath);
|
|
1310
1330
|
stashService = new StashService(projectPath, worktreeManager, persistence, broadcast);
|
|
@@ -1312,7 +1332,7 @@ function createWebSocketHandler(projectPath, userDevPort) {
|
|
|
1312
1332
|
open(ws) {
|
|
1313
1333
|
clients.add(ws);
|
|
1314
1334
|
logger.info("ws", "client connected", { total: clients.size });
|
|
1315
|
-
ws.send(JSON.stringify({ type: "server_ready", port: userDevPort }));
|
|
1335
|
+
ws.send(JSON.stringify({ type: "server_ready", port: userDevPort, appProxyPort }));
|
|
1316
1336
|
},
|
|
1317
1337
|
async message(ws, message) {
|
|
1318
1338
|
const raw = typeof message === "string" ? message : new TextDecoder().decode(message);
|
|
@@ -1339,7 +1359,7 @@ function createWebSocketHandler(projectPath, userDevPort) {
|
|
|
1339
1359
|
type: "text",
|
|
1340
1360
|
createdAt: new Date().toISOString()
|
|
1341
1361
|
});
|
|
1342
|
-
await stashService.chat(event.projectId, event.message);
|
|
1362
|
+
await stashService.chat(event.projectId, event.message, event.referenceStashIds);
|
|
1343
1363
|
break;
|
|
1344
1364
|
case "generate":
|
|
1345
1365
|
persistence.saveChatMessage(event.projectId, {
|
|
@@ -1349,7 +1369,7 @@ function createWebSocketHandler(projectPath, userDevPort) {
|
|
|
1349
1369
|
type: "text",
|
|
1350
1370
|
createdAt: new Date().toISOString()
|
|
1351
1371
|
});
|
|
1352
|
-
await stashService.generate(event.projectId, event.prompt, event.stashCount);
|
|
1372
|
+
await stashService.generate(event.projectId, event.prompt, event.stashCount, event.referenceStashIds);
|
|
1353
1373
|
break;
|
|
1354
1374
|
case "vary":
|
|
1355
1375
|
await stashService.vary(event.sourceStashId, event.prompt);
|
|
@@ -1377,6 +1397,111 @@ function createWebSocketHandler(projectPath, userDevPort) {
|
|
|
1377
1397
|
};
|
|
1378
1398
|
}
|
|
1379
1399
|
|
|
1400
|
+
// ../server/dist/services/app-proxy.js
|
|
1401
|
+
function startAppProxy(userDevPort, proxyPort, injectOverlay) {
|
|
1402
|
+
const server = Bun.serve({
|
|
1403
|
+
port: proxyPort,
|
|
1404
|
+
async fetch(req, server2) {
|
|
1405
|
+
if (req.headers.get("upgrade")?.toLowerCase() === "websocket") {
|
|
1406
|
+
const url2 = new URL(req.url);
|
|
1407
|
+
const success = server2.upgrade(req, {
|
|
1408
|
+
data: {
|
|
1409
|
+
path: url2.pathname + url2.search,
|
|
1410
|
+
upstream: null,
|
|
1411
|
+
ready: false,
|
|
1412
|
+
buffer: []
|
|
1413
|
+
}
|
|
1414
|
+
});
|
|
1415
|
+
return success ? undefined : new Response("WebSocket upgrade failed", { status: 400 });
|
|
1416
|
+
}
|
|
1417
|
+
const url = new URL(req.url);
|
|
1418
|
+
const targetUrl = `http://localhost:${userDevPort}${url.pathname}${url.search}`;
|
|
1419
|
+
try {
|
|
1420
|
+
const headers = new Headers;
|
|
1421
|
+
for (const [key, value] of req.headers.entries()) {
|
|
1422
|
+
if (!["host", "accept-encoding"].includes(key.toLowerCase())) {
|
|
1423
|
+
headers.set(key, value);
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
headers.set("host", `localhost:${userDevPort}`);
|
|
1427
|
+
const response = await fetch(targetUrl, {
|
|
1428
|
+
method: req.method,
|
|
1429
|
+
headers,
|
|
1430
|
+
body: req.method !== "GET" && req.method !== "HEAD" ? await req.arrayBuffer() : undefined,
|
|
1431
|
+
redirect: "manual"
|
|
1432
|
+
});
|
|
1433
|
+
const contentType = response.headers.get("content-type") || "";
|
|
1434
|
+
if (contentType.includes("text/html")) {
|
|
1435
|
+
const html = await response.text();
|
|
1436
|
+
const injectedHtml = injectOverlay(html);
|
|
1437
|
+
const respHeaders2 = new Headers(response.headers);
|
|
1438
|
+
respHeaders2.delete("content-encoding");
|
|
1439
|
+
respHeaders2.delete("content-length");
|
|
1440
|
+
return new Response(injectedHtml, {
|
|
1441
|
+
status: response.status,
|
|
1442
|
+
headers: respHeaders2
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
const respHeaders = new Headers(response.headers);
|
|
1446
|
+
respHeaders.delete("content-encoding");
|
|
1447
|
+
return new Response(response.body, {
|
|
1448
|
+
status: response.status,
|
|
1449
|
+
headers: respHeaders
|
|
1450
|
+
});
|
|
1451
|
+
} catch (err) {
|
|
1452
|
+
return new Response(JSON.stringify({ error: "Proxy failed", detail: String(err) }), {
|
|
1453
|
+
status: 502,
|
|
1454
|
+
headers: { "content-type": "application/json" }
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
},
|
|
1458
|
+
websocket: {
|
|
1459
|
+
open(ws) {
|
|
1460
|
+
const { data } = ws;
|
|
1461
|
+
const upstream = new WebSocket(`ws://localhost:${userDevPort}${data.path}`);
|
|
1462
|
+
upstream.addEventListener("open", () => {
|
|
1463
|
+
data.upstream = upstream;
|
|
1464
|
+
data.ready = true;
|
|
1465
|
+
for (const msg of data.buffer) {
|
|
1466
|
+
upstream.send(msg);
|
|
1467
|
+
}
|
|
1468
|
+
data.buffer = [];
|
|
1469
|
+
});
|
|
1470
|
+
upstream.addEventListener("message", (event) => {
|
|
1471
|
+
try {
|
|
1472
|
+
ws.sendText(typeof event.data === "string" ? event.data : String(event.data));
|
|
1473
|
+
} catch {}
|
|
1474
|
+
});
|
|
1475
|
+
upstream.addEventListener("close", () => {
|
|
1476
|
+
try {
|
|
1477
|
+
ws.close();
|
|
1478
|
+
} catch {}
|
|
1479
|
+
});
|
|
1480
|
+
upstream.addEventListener("error", () => {
|
|
1481
|
+
try {
|
|
1482
|
+
ws.close();
|
|
1483
|
+
} catch {}
|
|
1484
|
+
});
|
|
1485
|
+
},
|
|
1486
|
+
message(ws, msg) {
|
|
1487
|
+
const { data } = ws;
|
|
1488
|
+
if (data.ready && data.upstream) {
|
|
1489
|
+
data.upstream.send(typeof msg === "string" ? msg : new Uint8Array(msg));
|
|
1490
|
+
} else {
|
|
1491
|
+
data.buffer.push(typeof msg === "string" ? msg : new Uint8Array(msg).buffer);
|
|
1492
|
+
}
|
|
1493
|
+
},
|
|
1494
|
+
close(ws) {
|
|
1495
|
+
try {
|
|
1496
|
+
ws.data.upstream?.close();
|
|
1497
|
+
} catch {}
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
});
|
|
1501
|
+
logger.info("proxy", `App proxy at http://localhost:${proxyPort} \u2192 http://localhost:${userDevPort}`);
|
|
1502
|
+
return server;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1380
1505
|
// ../server/dist/index.js
|
|
1381
1506
|
var serverState = {
|
|
1382
1507
|
projectPath: "",
|
|
@@ -1388,56 +1513,6 @@ function getPersistence() {
|
|
|
1388
1513
|
var app2 = new Hono2;
|
|
1389
1514
|
app2.use("/*", cors());
|
|
1390
1515
|
app2.route("/api", apiRoutes);
|
|
1391
|
-
async function proxyToUserApp(c, targetPath, injectOverlay = false) {
|
|
1392
|
-
const userDevPort = serverState.userDevPort;
|
|
1393
|
-
const url = new URL(c.req.url);
|
|
1394
|
-
const targetUrl = `http://localhost:${userDevPort}${targetPath}${url.search}`;
|
|
1395
|
-
try {
|
|
1396
|
-
const headers = new Headers;
|
|
1397
|
-
for (const [key, value] of Object.entries(c.req.header())) {
|
|
1398
|
-
if (!["host", "accept-encoding"].includes(key.toLowerCase()) && value) {
|
|
1399
|
-
headers.set(key, value);
|
|
1400
|
-
}
|
|
1401
|
-
}
|
|
1402
|
-
headers.set("host", `localhost:${userDevPort}`);
|
|
1403
|
-
const response = await fetch(targetUrl, {
|
|
1404
|
-
method: c.req.method,
|
|
1405
|
-
headers,
|
|
1406
|
-
body: c.req.method !== "GET" && c.req.method !== "HEAD" ? await c.req.arrayBuffer() : undefined,
|
|
1407
|
-
redirect: "manual"
|
|
1408
|
-
});
|
|
1409
|
-
const contentType = response.headers.get("content-type") || "";
|
|
1410
|
-
if (injectOverlay && contentType.includes("text/html")) {
|
|
1411
|
-
const html = await response.text();
|
|
1412
|
-
const injectedHtml = injectOverlayScript(html);
|
|
1413
|
-
const respHeaders2 = new Headers(response.headers);
|
|
1414
|
-
respHeaders2.delete("content-encoding");
|
|
1415
|
-
respHeaders2.delete("content-length");
|
|
1416
|
-
return new Response(injectedHtml, {
|
|
1417
|
-
status: response.status,
|
|
1418
|
-
headers: respHeaders2
|
|
1419
|
-
});
|
|
1420
|
-
}
|
|
1421
|
-
const respHeaders = new Headers(response.headers);
|
|
1422
|
-
respHeaders.delete("content-encoding");
|
|
1423
|
-
return new Response(response.body, {
|
|
1424
|
-
status: response.status,
|
|
1425
|
-
headers: respHeaders
|
|
1426
|
-
});
|
|
1427
|
-
} catch (err) {
|
|
1428
|
-
return new Response(JSON.stringify({ error: "Proxy failed", detail: String(err) }), {
|
|
1429
|
-
status: 502,
|
|
1430
|
-
headers: { "content-type": "application/json" }
|
|
1431
|
-
});
|
|
1432
|
-
}
|
|
1433
|
-
}
|
|
1434
|
-
app2.all("/app/*", (c) => {
|
|
1435
|
-
const path = c.req.path.replace(/^\/app/, "") || "/";
|
|
1436
|
-
return proxyToUserApp(c, path, true);
|
|
1437
|
-
});
|
|
1438
|
-
app2.all("/_next/*", (c) => proxyToUserApp(c, c.req.path));
|
|
1439
|
-
app2.all("/__nextjs*", (c) => proxyToUserApp(c, c.req.path));
|
|
1440
|
-
app2.all("/__next*", (c) => proxyToUserApp(c, c.req.path));
|
|
1441
1516
|
app2.get("/*", async (c) => {
|
|
1442
1517
|
const path = c.req.path;
|
|
1443
1518
|
const selfDir = dirname2(fileURLToPath(import.meta.url));
|
|
@@ -1480,7 +1555,9 @@ app2.get("/*", async (c) => {
|
|
|
1480
1555
|
function startServer(projectPath, userDevPort, port = STASHES_PORT) {
|
|
1481
1556
|
serverState = { projectPath, userDevPort };
|
|
1482
1557
|
initLogFile(projectPath);
|
|
1483
|
-
const
|
|
1558
|
+
const appProxyPort = port + 1;
|
|
1559
|
+
startAppProxy(userDevPort, appProxyPort, injectOverlayScript);
|
|
1560
|
+
const wsHandler = createWebSocketHandler(projectPath, userDevPort, appProxyPort);
|
|
1484
1561
|
const server = Bun.serve({
|
|
1485
1562
|
port,
|
|
1486
1563
|
fetch(req, server2) {
|
|
@@ -1645,22 +1722,25 @@ function injectOverlayScript(html) {
|
|
|
1645
1722
|
}
|
|
1646
1723
|
});
|
|
1647
1724
|
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1725
|
+
// Report current URL to parent for status bar display
|
|
1726
|
+
function reportUrl() {
|
|
1727
|
+
window.parent.postMessage({
|
|
1728
|
+
type: 'stashes:url_change',
|
|
1729
|
+
url: window.location.pathname + window.location.search + window.location.hash
|
|
1730
|
+
}, '*');
|
|
1731
|
+
}
|
|
1732
|
+
reportUrl();
|
|
1733
|
+
var origPush = history.pushState;
|
|
1734
|
+
history.pushState = function() {
|
|
1735
|
+
origPush.apply(this, arguments);
|
|
1736
|
+
setTimeout(reportUrl, 0);
|
|
1737
|
+
};
|
|
1738
|
+
var origReplace = history.replaceState;
|
|
1739
|
+
history.replaceState = function() {
|
|
1740
|
+
origReplace.apply(this, arguments);
|
|
1741
|
+
setTimeout(reportUrl, 0);
|
|
1742
|
+
};
|
|
1743
|
+
window.addEventListener('popstate', reportUrl);
|
|
1664
1744
|
|
|
1665
1745
|
document.addEventListener('mousemove', onMouseMove, { passive: true });
|
|
1666
1746
|
document.addEventListener('click', onClick, true);
|