stashes 0.1.47 → 0.1.48

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 CHANGED
@@ -40,6 +40,10 @@ var DEFAULT_STASH_COUNT = 3;
40
40
  var APP_PROXY_PORT = STASHES_PORT + 1;
41
41
  var STASH_PORT_START = 4010;
42
42
  var STASH_PORT_END = 4030;
43
+ var STASH_PORT_OFFSET = 10;
44
+ var STASH_PORT_RANGE = 20;
45
+ var PORT_SLOT_SIZE = 100;
46
+ var MAX_PORT_SLOTS = 10;
43
47
  var MAX_PREVIEW_SERVERS = 5;
44
48
  var PREVIEW_TTL_MS = 300000;
45
49
  var PREVIEW_REAPER_INTERVAL = 30000;
@@ -1683,14 +1687,18 @@ class PreviewPool {
1683
1687
  usedPorts = new Set;
1684
1688
  maxSize;
1685
1689
  ttlMs;
1690
+ portStart;
1691
+ portEnd;
1686
1692
  worktreeManager;
1687
1693
  broadcast;
1688
1694
  reaperInterval;
1689
- constructor(worktreeManager, broadcast, maxSize = MAX_PREVIEW_SERVERS, ttlMs = PREVIEW_TTL_MS) {
1695
+ constructor(worktreeManager, broadcast, maxSize = MAX_PREVIEW_SERVERS, ttlMs = PREVIEW_TTL_MS, portStart = STASH_PORT_START, portEnd = STASH_PORT_END) {
1690
1696
  this.worktreeManager = worktreeManager;
1691
1697
  this.broadcast = broadcast;
1692
1698
  this.maxSize = maxSize;
1693
1699
  this.ttlMs = ttlMs;
1700
+ this.portStart = portStart;
1701
+ this.portEnd = portEnd;
1694
1702
  this.reaperInterval = setInterval(() => this.reap(), PREVIEW_REAPER_INTERVAL);
1695
1703
  }
1696
1704
  async getOrStart(stashId) {
@@ -1834,12 +1842,12 @@ class PreviewPool {
1834
1842
  }
1835
1843
  }
1836
1844
  allocatePort() {
1837
- for (let port = STASH_PORT_START;port <= STASH_PORT_END; port++) {
1845
+ for (let port = this.portStart;port <= this.portEnd; port++) {
1838
1846
  if (!this.usedPorts.has(port)) {
1839
1847
  return port;
1840
1848
  }
1841
1849
  }
1842
- throw new Error(`No available ports in range ${STASH_PORT_START}-${STASH_PORT_END}`);
1850
+ throw new Error(`No available ports in range ${this.portStart}-${this.portEnd}`);
1843
1851
  }
1844
1852
  killEntry(entry) {
1845
1853
  try {
@@ -1877,12 +1885,12 @@ class StashService {
1877
1885
  chatSessions = new Map;
1878
1886
  stashPollTimer = null;
1879
1887
  knownStashIds = new Set;
1880
- constructor(projectPath, worktreeManager, persistence, broadcast) {
1888
+ constructor(projectPath, worktreeManager, persistence, broadcast, stashPortStart, stashPortEnd) {
1881
1889
  this.projectPath = projectPath;
1882
1890
  this.worktreeManager = worktreeManager;
1883
1891
  this.persistence = persistence;
1884
1892
  this.broadcast = broadcast;
1885
- this.previewPool = new PreviewPool(worktreeManager, broadcast);
1893
+ this.previewPool = new PreviewPool(worktreeManager, broadcast, undefined, undefined, stashPortStart, stashPortEnd);
1886
1894
  }
1887
1895
  getActiveChatId() {
1888
1896
  return this.activeChatId;
@@ -1909,7 +1917,7 @@ class StashService {
1909
1917
  "Reply with ONLY the file path relative to the project root."
1910
1918
  ].join(`
1911
1919
  `);
1912
- const aiProcess = startAiProcess("resolve-component", prompt, this.projectPath);
1920
+ const aiProcess = startAiProcess("resolve-component", prompt, this.projectPath, undefined, "claude-haiku-4-5-20251001");
1913
1921
  let resolvedPath = "";
1914
1922
  try {
1915
1923
  for await (const chunk of parseClaudeStream(aiProcess.process)) {
@@ -2295,10 +2303,10 @@ function broadcast(event) {
2295
2303
  function getPersistenceFromWs() {
2296
2304
  return persistence;
2297
2305
  }
2298
- function createWebSocketHandler(projectPath, userDevPort, appProxyPort) {
2306
+ function createWebSocketHandler(projectPath, userDevPort, appProxyPort, stashPortStart, stashPortEnd) {
2299
2307
  worktreeManager = new WorktreeManager(projectPath);
2300
2308
  persistence = new PersistenceService(projectPath);
2301
- stashService = new StashService(projectPath, worktreeManager, persistence, broadcast);
2309
+ stashService = new StashService(projectPath, worktreeManager, persistence, broadcast, stashPortStart, stashPortEnd);
2302
2310
  return {
2303
2311
  open(ws) {
2304
2312
  clients.add(ws);
@@ -2432,17 +2440,37 @@ app2.get("/*", async (c) => {
2432
2440
  headers: { "content-type": "application/json" }
2433
2441
  });
2434
2442
  });
2435
- function startServer(projectPath, userDevPort, port = STASHES_PORT) {
2443
+ async function isPortAvailable(port) {
2444
+ try {
2445
+ const server = Bun.serve({ port, fetch: () => new Response("") });
2446
+ server.stop(true);
2447
+ return true;
2448
+ } catch {
2449
+ return false;
2450
+ }
2451
+ }
2452
+ async function findAvailablePort(requestedPort) {
2453
+ for (let slot = 0;slot < MAX_PORT_SLOTS; slot++) {
2454
+ const port = requestedPort + slot * PORT_SLOT_SIZE;
2455
+ if (await isPortAvailable(port))
2456
+ return port;
2457
+ }
2458
+ throw new Error(`No available port found. Tried ${MAX_PORT_SLOTS} slots starting from ${requestedPort} (step ${PORT_SLOT_SIZE}).`);
2459
+ }
2460
+ async function startServer(projectPath, userDevPort, requestedPort = STASHES_PORT) {
2461
+ const port = await findAvailablePort(requestedPort);
2462
+ const appProxyPort = port + 1;
2463
+ const stashPortStart = port + STASH_PORT_OFFSET;
2464
+ const stashPortEnd = port + STASH_PORT_OFFSET + STASH_PORT_RANGE;
2436
2465
  serverState = { projectPath, userDevPort };
2437
2466
  initLogFile(projectPath);
2438
- const appProxyPort = port + 1;
2439
2467
  startAppProxy(userDevPort, appProxyPort, injectOverlayScript);
2440
- const wsHandler = createWebSocketHandler(projectPath, userDevPort, appProxyPort);
2441
- const server = Bun.serve({
2468
+ const wsHandler = createWebSocketHandler(projectPath, userDevPort, appProxyPort, stashPortStart, stashPortEnd);
2469
+ Bun.serve({
2442
2470
  port,
2443
- fetch(req, server2) {
2471
+ fetch(req, server) {
2444
2472
  if (req.headers.get("upgrade") === "websocket") {
2445
- const success = server2.upgrade(req, {
2473
+ const success = server.upgrade(req, {
2446
2474
  data: { projectPath, userDevPort }
2447
2475
  });
2448
2476
  if (success)
@@ -2455,7 +2483,10 @@ function startServer(projectPath, userDevPort, port = STASHES_PORT) {
2455
2483
  logger.info("server", `Stashes running at http://localhost:${port}`);
2456
2484
  logger.info("server", `Proxying user app from http://localhost:${userDevPort}`);
2457
2485
  logger.info("server", `Project: ${projectPath}`);
2458
- return server;
2486
+ if (port !== requestedPort) {
2487
+ logger.info("server", `Port ${requestedPort} was in use, using ${port} instead`);
2488
+ }
2489
+ return { port, appProxyPort, stashPortStart, stashPortEnd };
2459
2490
  }
2460
2491
 
2461
2492
  // ../server/dist/services/detector.js
@@ -2535,7 +2566,7 @@ function findConfig(projectPath, candidates) {
2535
2566
  // src/commands/start.ts
2536
2567
  async function startCommand(path, options) {
2537
2568
  const projectPath = resolve(path || ".");
2538
- const port = parseInt(options.port, 10);
2569
+ const requestedPort = parseInt(options.port, 10);
2539
2570
  console.log("");
2540
2571
  console.log(" \u2554\u2550\u2557\u2554\u2566\u2557\u2554\u2550\u2557\u2554\u2550\u2557\u2566 \u2566\u2554\u2550\u2557\u2554\u2550\u2557");
2541
2572
  console.log(" \u255A\u2550\u2557 \u2551 \u2560\u2550\u2563\u255A\u2550\u2557\u2560\u2550\u2563\u2551\u2563 \u255A\u2550\u2557");
@@ -2546,17 +2577,20 @@ async function startCommand(path, options) {
2546
2577
  console.log(` Project: ${projectPath}`);
2547
2578
  console.log(` Framework: ${detected.framework}`);
2548
2579
  console.log(` Dev server: http://localhost:${devPort}`);
2549
- console.log(` Stashes: http://localhost:${port}`);
2550
- console.log("");
2551
2580
  const isDevRunning = await checkPort(devPort);
2552
2581
  if (!isDevRunning) {
2582
+ console.log("");
2553
2583
  console.log(` ! Your dev server is not running on port ${devPort}`);
2554
2584
  console.log(` Start it with: ${detected.devCommand}`);
2555
- console.log("");
2556
2585
  }
2557
- startServer(projectPath, devPort, port);
2586
+ const result = await startServer(projectPath, devPort, requestedPort);
2587
+ console.log(` Stashes: http://localhost:${result.port}`);
2588
+ if (result.port !== requestedPort) {
2589
+ console.log(` (port ${requestedPort} was in use, using ${result.port})`);
2590
+ }
2591
+ console.log("");
2558
2592
  if (options.open !== false) {
2559
- await open(`http://localhost:${port}`);
2593
+ await open(`http://localhost:${result.port}`);
2560
2594
  }
2561
2595
  }
2562
2596
  async function checkPort(port) {
@@ -1 +1 @@
1
- {"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"AAMA,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;CACf;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA+BrF"}
1
+ {"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"AAMA,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;CACf;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAmCrF"}
@@ -4,7 +4,7 @@ import { startServer } from '@stashes/server';
4
4
  import { detectFramework } from '@stashes/server/services/detector';
5
5
  export async function startCommand(path, options) {
6
6
  const projectPath = resolve(path || '.');
7
- const port = parseInt(options.port, 10);
7
+ const requestedPort = parseInt(options.port, 10);
8
8
  console.log('');
9
9
  console.log(' ╔═╗╔╦╗╔═╗╔═╗╦ ╦╔═╗╔═╗');
10
10
  console.log(' ╚═╗ ║ ╠═╣╚═╗╠═╣║╣ ╚═╗');
@@ -15,17 +15,20 @@ export async function startCommand(path, options) {
15
15
  console.log(` Project: ${projectPath}`);
16
16
  console.log(` Framework: ${detected.framework}`);
17
17
  console.log(` Dev server: http://localhost:${devPort}`);
18
- console.log(` Stashes: http://localhost:${port}`);
19
- console.log('');
20
18
  const isDevRunning = await checkPort(devPort);
21
19
  if (!isDevRunning) {
20
+ console.log('');
22
21
  console.log(` ! Your dev server is not running on port ${devPort}`);
23
22
  console.log(` Start it with: ${detected.devCommand}`);
24
- console.log('');
25
23
  }
26
- startServer(projectPath, devPort, port);
24
+ const result = await startServer(projectPath, devPort, requestedPort);
25
+ console.log(` Stashes: http://localhost:${result.port}`);
26
+ if (result.port !== requestedPort) {
27
+ console.log(` (port ${requestedPort} was in use, using ${result.port})`);
28
+ }
29
+ console.log('');
27
30
  if (options.open !== false) {
28
- await open(`http://localhost:${port}`);
31
+ await open(`http://localhost:${result.port}`);
29
32
  }
30
33
  }
31
34
  async function checkPort(port) {
@@ -1 +1 @@
1
- {"version":3,"file":"start.js","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC/B,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AASpE,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAY,EAAE,OAAqB;IACpE,MAAM,WAAW,GAAG,OAAO,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAExC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;IACvC,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;IACvC,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;IACvC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,MAAM,QAAQ,GAAG,eAAe,CAAC,WAAW,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,OAAO,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;IAE1E,OAAO,CAAC,GAAG,CAAC,iBAAiB,WAAW,EAAE,CAAC,CAAC;IAC5C,OAAO,CAAC,GAAG,CAAC,iBAAiB,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC;IACnD,OAAO,CAAC,GAAG,CAAC,kCAAkC,OAAO,EAAE,CAAC,CAAC;IACzD,OAAO,CAAC,GAAG,CAAC,kCAAkC,IAAI,EAAE,CAAC,CAAC;IACtD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,MAAM,YAAY,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC;IAC9C,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,OAAO,CAAC,GAAG,CAAC,8CAA8C,OAAO,EAAE,CAAC,CAAC;QACrE,OAAO,CAAC,GAAG,CAAC,sBAAsB,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;QACzD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAClB,CAAC;IAED,WAAW,CAAC,WAAW,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAExC,IAAI,OAAO,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QAC3B,MAAM,IAAI,CAAC,oBAAoB,IAAI,EAAE,CAAC,CAAC;IACzC,CAAC;AACH,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,IAAY;IACnC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,oBAAoB,IAAI,EAAE,EAAE;YACvD,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC;SAClC,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC,EAAE,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"start.js","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC/B,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AASpE,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAY,EAAE,OAAqB;IACpE,MAAM,WAAW,GAAG,OAAO,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC;IACzC,MAAM,aAAa,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAEjD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;IACvC,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;IACvC,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;IACvC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,MAAM,QAAQ,GAAG,eAAe,CAAC,WAAW,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,OAAO,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;IAE1E,OAAO,CAAC,GAAG,CAAC,iBAAiB,WAAW,EAAE,CAAC,CAAC;IAC5C,OAAO,CAAC,GAAG,CAAC,iBAAiB,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC;IACnD,OAAO,CAAC,GAAG,CAAC,kCAAkC,OAAO,EAAE,CAAC,CAAC;IAEzD,MAAM,YAAY,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC;IAC9C,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,8CAA8C,OAAO,EAAE,CAAC,CAAC;QACrE,OAAO,CAAC,GAAG,CAAC,sBAAsB,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,WAAW,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;IAEtE,OAAO,CAAC,GAAG,CAAC,kCAAkC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IAC7D,IAAI,MAAM,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;QAClC,OAAO,CAAC,GAAG,CAAC,WAAW,aAAa,sBAAsB,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC;IAC5E,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,IAAI,OAAO,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QAC3B,MAAM,IAAI,CAAC,oBAAoB,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;AACH,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,IAAY;IACnC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,oBAAoB,IAAI,EAAE,EAAE;YACvD,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC;SAClC,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC,EAAE,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
package/dist/mcp.js CHANGED
@@ -36,6 +36,10 @@ var DEFAULT_STASH_COUNT = 3;
36
36
  var APP_PROXY_PORT = STASHES_PORT + 1;
37
37
  var STASH_PORT_START = 4010;
38
38
  var STASH_PORT_END = 4030;
39
+ var STASH_PORT_OFFSET = 10;
40
+ var STASH_PORT_RANGE = 20;
41
+ var PORT_SLOT_SIZE = 100;
42
+ var MAX_PORT_SLOTS = 10;
39
43
  var MAX_PREVIEW_SERVERS = 5;
40
44
  var PREVIEW_TTL_MS = 300000;
41
45
  var PREVIEW_REAPER_INTERVAL = 30000;
@@ -1879,14 +1883,18 @@ class PreviewPool {
1879
1883
  usedPorts = new Set;
1880
1884
  maxSize;
1881
1885
  ttlMs;
1886
+ portStart;
1887
+ portEnd;
1882
1888
  worktreeManager;
1883
1889
  broadcast;
1884
1890
  reaperInterval;
1885
- constructor(worktreeManager, broadcast, maxSize = MAX_PREVIEW_SERVERS, ttlMs = PREVIEW_TTL_MS) {
1891
+ constructor(worktreeManager, broadcast, maxSize = MAX_PREVIEW_SERVERS, ttlMs = PREVIEW_TTL_MS, portStart = STASH_PORT_START, portEnd = STASH_PORT_END) {
1886
1892
  this.worktreeManager = worktreeManager;
1887
1893
  this.broadcast = broadcast;
1888
1894
  this.maxSize = maxSize;
1889
1895
  this.ttlMs = ttlMs;
1896
+ this.portStart = portStart;
1897
+ this.portEnd = portEnd;
1890
1898
  this.reaperInterval = setInterval(() => this.reap(), PREVIEW_REAPER_INTERVAL);
1891
1899
  }
1892
1900
  async getOrStart(stashId) {
@@ -2030,12 +2038,12 @@ class PreviewPool {
2030
2038
  }
2031
2039
  }
2032
2040
  allocatePort() {
2033
- for (let port = STASH_PORT_START;port <= STASH_PORT_END; port++) {
2041
+ for (let port = this.portStart;port <= this.portEnd; port++) {
2034
2042
  if (!this.usedPorts.has(port)) {
2035
2043
  return port;
2036
2044
  }
2037
2045
  }
2038
- throw new Error(`No available ports in range ${STASH_PORT_START}-${STASH_PORT_END}`);
2046
+ throw new Error(`No available ports in range ${this.portStart}-${this.portEnd}`);
2039
2047
  }
2040
2048
  killEntry(entry) {
2041
2049
  try {
@@ -2073,12 +2081,12 @@ class StashService {
2073
2081
  chatSessions = new Map;
2074
2082
  stashPollTimer = null;
2075
2083
  knownStashIds = new Set;
2076
- constructor(projectPath, worktreeManager, persistence, broadcast) {
2084
+ constructor(projectPath, worktreeManager, persistence, broadcast, stashPortStart, stashPortEnd) {
2077
2085
  this.projectPath = projectPath;
2078
2086
  this.worktreeManager = worktreeManager;
2079
2087
  this.persistence = persistence;
2080
2088
  this.broadcast = broadcast;
2081
- this.previewPool = new PreviewPool(worktreeManager, broadcast);
2089
+ this.previewPool = new PreviewPool(worktreeManager, broadcast, undefined, undefined, stashPortStart, stashPortEnd);
2082
2090
  }
2083
2091
  getActiveChatId() {
2084
2092
  return this.activeChatId;
@@ -2105,7 +2113,7 @@ class StashService {
2105
2113
  "Reply with ONLY the file path relative to the project root."
2106
2114
  ].join(`
2107
2115
  `);
2108
- const aiProcess = startAiProcess("resolve-component", prompt, this.projectPath);
2116
+ const aiProcess = startAiProcess("resolve-component", prompt, this.projectPath, undefined, "claude-haiku-4-5-20251001");
2109
2117
  let resolvedPath = "";
2110
2118
  try {
2111
2119
  for await (const chunk of parseClaudeStream(aiProcess.process)) {
@@ -2491,10 +2499,10 @@ function broadcast(event) {
2491
2499
  function getPersistenceFromWs() {
2492
2500
  return persistence;
2493
2501
  }
2494
- function createWebSocketHandler(projectPath, userDevPort, appProxyPort) {
2502
+ function createWebSocketHandler(projectPath, userDevPort, appProxyPort, stashPortStart, stashPortEnd) {
2495
2503
  worktreeManager = new WorktreeManager(projectPath);
2496
2504
  persistence = new PersistenceService(projectPath);
2497
- stashService = new StashService(projectPath, worktreeManager, persistence, broadcast);
2505
+ stashService = new StashService(projectPath, worktreeManager, persistence, broadcast, stashPortStart, stashPortEnd);
2498
2506
  return {
2499
2507
  open(ws) {
2500
2508
  clients.add(ws);
@@ -2628,17 +2636,37 @@ app2.get("/*", async (c) => {
2628
2636
  headers: { "content-type": "application/json" }
2629
2637
  });
2630
2638
  });
2631
- function startServer(projectPath, userDevPort, port = STASHES_PORT) {
2639
+ async function isPortAvailable(port) {
2640
+ try {
2641
+ const server = Bun.serve({ port, fetch: () => new Response("") });
2642
+ server.stop(true);
2643
+ return true;
2644
+ } catch {
2645
+ return false;
2646
+ }
2647
+ }
2648
+ async function findAvailablePort(requestedPort) {
2649
+ for (let slot = 0;slot < MAX_PORT_SLOTS; slot++) {
2650
+ const port = requestedPort + slot * PORT_SLOT_SIZE;
2651
+ if (await isPortAvailable(port))
2652
+ return port;
2653
+ }
2654
+ throw new Error(`No available port found. Tried ${MAX_PORT_SLOTS} slots starting from ${requestedPort} (step ${PORT_SLOT_SIZE}).`);
2655
+ }
2656
+ async function startServer(projectPath, userDevPort, requestedPort = STASHES_PORT) {
2657
+ const port = await findAvailablePort(requestedPort);
2658
+ const appProxyPort = port + 1;
2659
+ const stashPortStart = port + STASH_PORT_OFFSET;
2660
+ const stashPortEnd = port + STASH_PORT_OFFSET + STASH_PORT_RANGE;
2632
2661
  serverState = { projectPath, userDevPort };
2633
2662
  initLogFile(projectPath);
2634
- const appProxyPort = port + 1;
2635
2663
  startAppProxy(userDevPort, appProxyPort, injectOverlayScript);
2636
- const wsHandler = createWebSocketHandler(projectPath, userDevPort, appProxyPort);
2637
- const server = Bun.serve({
2664
+ const wsHandler = createWebSocketHandler(projectPath, userDevPort, appProxyPort, stashPortStart, stashPortEnd);
2665
+ Bun.serve({
2638
2666
  port,
2639
- fetch(req, server2) {
2667
+ fetch(req, server) {
2640
2668
  if (req.headers.get("upgrade") === "websocket") {
2641
- const success = server2.upgrade(req, {
2669
+ const success = server.upgrade(req, {
2642
2670
  data: { projectPath, userDevPort }
2643
2671
  });
2644
2672
  if (success)
@@ -2651,7 +2679,10 @@ function startServer(projectPath, userDevPort, port = STASHES_PORT) {
2651
2679
  logger.info("server", `Stashes running at http://localhost:${port}`);
2652
2680
  logger.info("server", `Proxying user app from http://localhost:${userDevPort}`);
2653
2681
  logger.info("server", `Project: ${projectPath}`);
2654
- return server;
2682
+ if (port !== requestedPort) {
2683
+ logger.info("server", `Port ${requestedPort} was in use, using ${port} instead`);
2684
+ }
2685
+ return { port, appProxyPort, stashPortStart, stashPortEnd };
2655
2686
  }
2656
2687
 
2657
2688
  // ../mcp/src/tools/browse.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stashes",
3
- "version": "0.1.47",
3
+ "version": "0.1.48",
4
4
  "type": "module",
5
5
  "description": "Generate AI-powered UI design explorations in your project",
6
6
  "keywords": [