@portel/photon 1.8.3 → 1.9.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.
@@ -1 +1 @@
1
- {"version":3,"file":"beam.d.ts","sourceRoot":"","sources":["../../src/auto-ui/beam.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAiyBH,wBAAsB,SAAS,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAglFlF;AAiYD;;;GAGG;AACH,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAsB9C"}
1
+ {"version":3,"file":"beam.d.ts","sourceRoot":"","sources":["../../src/auto-ui/beam.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAiyBH,wBAAsB,SAAS,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAysFlF;AAiYD;;;GAGG;AACH,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAsB9C"}
@@ -588,6 +588,9 @@ function extractCspFromSource(source) {
588
588
  }
589
589
  export async function startBeam(rawWorkingDir, port) {
590
590
  const workingDir = path.resolve(rawWorkingDir);
591
+ // Show version banner immediately
592
+ const { PHOTON_VERSION } = await import('../version.js');
593
+ console.log(`\n⚡ Photon Beam v${PHOTON_VERSION}\n`);
591
594
  // Initialize marketplace manager for photon discovery and installation
592
595
  const marketplace = new MarketplaceManager();
593
596
  await marketplace.initialize();
@@ -598,6 +601,16 @@ export async function startBeam(rawWorkingDir, port) {
598
601
  catch (error) {
599
602
  logger.warn(`Failed to update marketplace caches: ${getErrorMessage(error)}`);
600
603
  }
604
+ // Repair missing assets from photons installed before the asset-download fix
605
+ try {
606
+ const repaired = await marketplace.repairMissingAssets(workingDir);
607
+ if (repaired > 0) {
608
+ logger.info(`Repaired assets for ${repaired} photon(s)`);
609
+ }
610
+ }
611
+ catch (error) {
612
+ logger.warn(`Asset repair check failed: ${getErrorMessage(error)}`);
613
+ }
601
614
  // Discover all photons (user photons + bundled photons)
602
615
  const userPhotonList = await listPhotonMCPs(workingDir);
603
616
  // Add bundled photons with their paths
@@ -1824,9 +1837,30 @@ export async function startBeam(rawWorkingDir, port) {
1824
1837
  if (url.pathname === '/api/marketplace/list') {
1825
1838
  res.setHeader('Content-Type', 'application/json');
1826
1839
  try {
1840
+ // Auto-refresh caches older than 5 minutes so updates are detected without manual Sync
1841
+ await marketplace.autoUpdateStaleCaches(5 * 60 * 1000);
1842
+ const { readLocalMetadata } = await import('../marketplace-manager.js');
1827
1843
  const allPhotons = await marketplace.getAllPhotons();
1844
+ const localMetadata = await readLocalMetadata();
1828
1845
  const photonList = [];
1829
1846
  for (const [name, { metadata, marketplace: mp }] of allPhotons) {
1847
+ const installed = photonMCPs.has(name);
1848
+ let hasUpdate = false;
1849
+ let latestVersion = '';
1850
+ if (installed) {
1851
+ const installMeta = localMetadata.photons[`${name}.photon.ts`];
1852
+ if (installMeta && metadata.hash) {
1853
+ // Primary: hash comparison (catches code changes without version bump)
1854
+ hasUpdate = installMeta.originalHash !== metadata.hash;
1855
+ }
1856
+ else if (installMeta && metadata.version) {
1857
+ // Fallback: version comparison
1858
+ hasUpdate = installMeta.version !== metadata.version;
1859
+ }
1860
+ if (hasUpdate) {
1861
+ latestVersion = metadata.version || '';
1862
+ }
1863
+ }
1830
1864
  photonList.push({
1831
1865
  name,
1832
1866
  description: metadata.description || '',
@@ -1836,7 +1870,9 @@ export async function startBeam(rawWorkingDir, port) {
1836
1870
  marketplace: mp.name,
1837
1871
  icon: metadata.icon,
1838
1872
  internal: metadata.internal,
1839
- installed: photonMCPs.has(name),
1873
+ installed,
1874
+ hasUpdate,
1875
+ latestVersion,
1840
1876
  });
1841
1877
  }
1842
1878
  res.writeHead(200);
@@ -1878,6 +1914,9 @@ export async function startBeam(rawWorkingDir, port) {
1878
1914
  const hash = (await import('../marketplace-manager.js')).calculateHash(result.content);
1879
1915
  await marketplace.savePhotonMetadata(`${name}.photon.ts`, result.marketplace, result.metadata, hash);
1880
1916
  }
1917
+ // Trigger immediate load so the photon appears in the sidebar right away
1918
+ // (don't wait for the file watcher which has debounce delay)
1919
+ handleFileChange(name);
1881
1920
  res.writeHead(200);
1882
1921
  res.end(JSON.stringify({
1883
1922
  success: true,
@@ -1885,8 +1924,6 @@ export async function startBeam(rawWorkingDir, port) {
1885
1924
  path: targetPath,
1886
1925
  version: result.metadata?.version,
1887
1926
  }));
1888
- // Broadcast to connected clients to reload photon list
1889
- broadcastPhotonChange();
1890
1927
  }
1891
1928
  catch {
1892
1929
  res.writeHead(500);
@@ -1895,6 +1932,55 @@ export async function startBeam(rawWorkingDir, port) {
1895
1932
  });
1896
1933
  return;
1897
1934
  }
1935
+ // Marketplace API: Remove/uninstall a photon
1936
+ if (url.pathname === '/api/marketplace/remove' && req.method === 'POST') {
1937
+ res.setHeader('Content-Type', 'application/json');
1938
+ const body = await readBody(req);
1939
+ try {
1940
+ const { name } = JSON.parse(body);
1941
+ if (!name) {
1942
+ res.writeHead(400);
1943
+ res.end(JSON.stringify({ error: 'Missing photon name' }));
1944
+ return;
1945
+ }
1946
+ const filePath = path.join(workingDir, `${name}.photon.ts`);
1947
+ if (!existsSync(filePath)) {
1948
+ res.writeHead(404);
1949
+ res.end(JSON.stringify({ error: `Photon '${name}' not found` }));
1950
+ return;
1951
+ }
1952
+ // Remove the .photon.ts file
1953
+ await fs.unlink(filePath);
1954
+ // Remove UI assets directory if it exists
1955
+ const assetsDir = path.join(workingDir, name);
1956
+ if (existsSync(assetsDir) && lstatSync(assetsDir).isDirectory()) {
1957
+ await fs.rm(assetsDir, { recursive: true });
1958
+ }
1959
+ // Clear compiled cache
1960
+ const cacheDir = path.join(os.homedir(), '.cache', 'photon-mcp', 'compiled');
1961
+ for (const ext of ['.js', '.js.map']) {
1962
+ try {
1963
+ await fs.unlink(path.join(cacheDir, `${name}${ext}`));
1964
+ }
1965
+ catch {
1966
+ /* ignore */
1967
+ }
1968
+ }
1969
+ // Remove from loaded photons
1970
+ const idx = photons.findIndex((p) => p.name === name);
1971
+ if (idx !== -1)
1972
+ photons.splice(idx, 1);
1973
+ photonMCPs.delete(name);
1974
+ res.writeHead(200);
1975
+ res.end(JSON.stringify({ success: true, name }));
1976
+ broadcastPhotonChange();
1977
+ }
1978
+ catch {
1979
+ res.writeHead(500);
1980
+ res.end(JSON.stringify({ error: 'Failed to remove photon' }));
1981
+ }
1982
+ return;
1983
+ }
1898
1984
  // Marketplace API: Get all marketplace sources
1899
1985
  if (url.pathname === '/api/marketplace/sources') {
1900
1986
  res.setHeader('Content-Type', 'application/json');
@@ -2058,18 +2144,24 @@ export async function startBeam(rawWorkingDir, port) {
2058
2144
  const { readLocalMetadata } = await import('../marketplace-manager.js');
2059
2145
  const localMetadata = await readLocalMetadata();
2060
2146
  const updates = [];
2061
- // Check each installed photon for updates
2147
+ // Check each installed photon for updates (hash-based primary, version fallback)
2062
2148
  for (const [fileName, installMeta] of Object.entries(localMetadata.photons)) {
2063
2149
  const photonName = fileName.replace(/\.photon\.ts$/, '');
2064
2150
  const latestInfo = await marketplace.getPhotonMetadata(photonName);
2065
- if (latestInfo && latestInfo.metadata.version !== installMeta.version) {
2066
- updates.push({
2067
- name: photonName,
2068
- fileName,
2069
- currentVersion: installMeta.version,
2070
- latestVersion: latestInfo.metadata.version,
2071
- marketplace: latestInfo.marketplace.name,
2072
- });
2151
+ if (latestInfo) {
2152
+ const hashChanged = latestInfo.metadata.hash
2153
+ ? installMeta.originalHash !== latestInfo.metadata.hash
2154
+ : false;
2155
+ const versionChanged = latestInfo.metadata.version !== installMeta.version;
2156
+ if (hashChanged || versionChanged) {
2157
+ updates.push({
2158
+ name: photonName,
2159
+ fileName,
2160
+ currentVersion: installMeta.version,
2161
+ latestVersion: latestInfo.metadata.version,
2162
+ marketplace: latestInfo.marketplace.name,
2163
+ });
2164
+ }
2073
2165
  }
2074
2166
  }
2075
2167
  res.writeHead(200);
@@ -2543,15 +2635,28 @@ export async function startBeam(rawWorkingDir, port) {
2543
2635
  socket.connect(p, '127.0.0.1');
2544
2636
  });
2545
2637
  };
2546
- // Find an available port
2638
+ // Find an available port (compact status line output)
2639
+ const isTTY = process.stderr.isTTY;
2547
2640
  while (currentPort < port + maxPortAttempts) {
2548
2641
  const available = await isPortAvailable(currentPort);
2549
- if (available)
2642
+ if (available) {
2643
+ // Clear the status line if we printed any
2644
+ if (currentPort > port && isTTY) {
2645
+ process.stderr.write('\r\x1b[K');
2646
+ }
2550
2647
  break;
2551
- console.error(`⚠️ Port ${currentPort} is in use, trying ${currentPort + 1}...`);
2648
+ }
2649
+ if (isTTY) {
2650
+ process.stderr.write(`\r\x1b[K⚠️ Port ${currentPort} in use, trying ${currentPort + 1}...`);
2651
+ }
2652
+ else {
2653
+ console.error(`⚠️ Port ${currentPort} is in use, trying ${currentPort + 1}...`);
2654
+ }
2552
2655
  currentPort++;
2553
2656
  }
2554
2657
  if (currentPort >= port + maxPortAttempts) {
2658
+ if (isTTY)
2659
+ process.stderr.write('\n');
2555
2660
  console.error(`\n❌ No available port found (tried ${port}-${currentPort - 1}). Exiting.\n`);
2556
2661
  process.exit(1);
2557
2662
  }
@@ -2560,7 +2665,12 @@ export async function startBeam(rawWorkingDir, port) {
2560
2665
  server.once('error', (err) => {
2561
2666
  if (err.code === 'EADDRINUSE' && currentPort < port + maxPortAttempts) {
2562
2667
  currentPort++;
2563
- console.error(`⚠️ Port ${currentPort - 1} is in use, trying ${currentPort}...`);
2668
+ if (isTTY) {
2669
+ process.stderr.write(`\r\x1b[K⚠️ Port ${currentPort - 1} in use, trying ${currentPort}...`);
2670
+ }
2671
+ else {
2672
+ console.error(`⚠️ Port ${currentPort - 1} is in use, trying ${currentPort}...`);
2673
+ }
2564
2674
  tryListen();
2565
2675
  }
2566
2676
  else if (err.code === 'EADDRINUSE') {
@@ -2577,7 +2687,9 @@ export async function startBeam(rawWorkingDir, port) {
2577
2687
  server.listen(currentPort, bindAddress, () => {
2578
2688
  process.env.BEAM_PORT = String(currentPort);
2579
2689
  const url = `http://localhost:${currentPort}`;
2580
- console.log(`\n⚡ Photon Beam → ${url} (loading photons...)\n`);
2690
+ if (isTTY)
2691
+ process.stderr.write('\r\x1b[K'); // Clear any port status line
2692
+ console.log(`⚡ Photon Beam → ${url} (loading photons...)\n`);
2581
2693
  resolve();
2582
2694
  });
2583
2695
  // Configure server and socket timeouts to prevent premature disconnections