@serendb/serendesktop 0.1.3 → 0.1.5

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.
Files changed (2) hide show
  1. package/dist/server.js +125 -38
  2. package/package.json +1 -1
package/dist/server.js CHANGED
@@ -4,9 +4,10 @@ import { exec as exec2 } from "child_process";
4
4
  import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
5
5
  import { createServer as createServer2 } from "http";
6
6
  import { request as httpsRequest } from "https";
7
- import { homedir as homedir5, platform as platform3 } from "os";
7
+ import { homedir as homedir6, platform as platform4 } from "os";
8
8
  import { join as join7, extname as extname2 } from "path";
9
9
  import { fileURLToPath as fileURLToPath2 } from "url";
10
+ import { createRequire as createRequire2 } from "module";
10
11
  import { WebSocketServer } from "ws";
11
12
 
12
13
  // src/events.ts
@@ -232,8 +233,8 @@ function createClient(sessionId) {
232
233
  toolCall: params.toolCall,
233
234
  options: params.options
234
235
  });
235
- const optionId = await new Promise((resolve3, reject) => {
236
- session.pendingPermissions.set(requestId, resolve3);
236
+ const optionId = await new Promise((resolve4, reject) => {
237
+ session.pendingPermissions.set(requestId, resolve4);
237
238
  setTimeout(() => {
238
239
  session.pendingPermissions.delete(requestId);
239
240
  reject(new Error("Permission request timed out"));
@@ -266,8 +267,8 @@ function createClient(sessionId) {
266
267
  oldText,
267
268
  newText: params.content
268
269
  });
269
- const accepted = await new Promise((resolve3, reject) => {
270
- session.pendingDiffProposals.set(proposalId, resolve3);
270
+ const accepted = await new Promise((resolve4, reject) => {
271
+ session.pendingDiffProposals.set(proposalId, resolve4);
271
272
  setTimeout(() => {
272
273
  session.pendingDiffProposals.delete(proposalId);
273
274
  reject(new Error("Diff proposal timed out"));
@@ -386,8 +387,8 @@ ${candidates.map((p) => ` - ${p}`).join("\n")}`
386
387
  }
387
388
  async function isCommandAvailable(command) {
388
389
  const which = platform() === "win32" ? "where" : "which";
389
- return new Promise((resolve3) => {
390
- execFile(which, [command], (err) => resolve3(!err));
390
+ return new Promise((resolve4) => {
391
+ execFile(which, [command], (err) => resolve4(!err));
391
392
  });
392
393
  }
393
394
  async function acpSpawn(params) {
@@ -622,7 +623,7 @@ async function acpEnsureClaudeCli() {
622
623
  return "claude";
623
624
  }
624
625
  const npmCmd = platform() === "win32" ? "npm.cmd" : "npm";
625
- return new Promise((resolve3, reject) => {
626
+ return new Promise((resolve4, reject) => {
626
627
  const proc = execFile(
627
628
  npmCmd,
628
629
  ["install", "-g", "@anthropic-ai/claude-code"],
@@ -636,7 +637,7 @@ async function acpEnsureClaudeCli() {
636
637
  return;
637
638
  }
638
639
  console.log(`[ACP] Claude Code CLI installed: ${stdout}`);
639
- resolve3("claude");
640
+ resolve4("claude");
640
641
  }
641
642
  );
642
643
  });
@@ -648,17 +649,17 @@ import { platform as platform2 } from "os";
648
649
  import { dirname } from "path";
649
650
  var os = platform2();
650
651
  function exec(cmd, args) {
651
- return new Promise((resolve3, reject) => {
652
+ return new Promise((resolve4, reject) => {
652
653
  execFile2(cmd, args, { timeout: 6e4 }, (err, stdout) => {
653
654
  if (err) {
654
655
  if (err.code === 1 || err.killed) {
655
- resolve3("");
656
+ resolve4("");
656
657
  return;
657
658
  }
658
659
  reject(err);
659
660
  return;
660
661
  }
661
- resolve3(stdout.trim());
662
+ resolve4(stdout.trim());
662
663
  });
663
664
  });
664
665
  }
@@ -1417,7 +1418,7 @@ import { randomUUID as randomUUID2 } from "crypto";
1417
1418
  var processes = /* @__PURE__ */ new Map();
1418
1419
  function sendRequest(proc, method, params) {
1419
1420
  const id = randomUUID2();
1420
- return new Promise((resolve3, reject) => {
1421
+ return new Promise((resolve4, reject) => {
1421
1422
  const timeout = setTimeout(() => {
1422
1423
  proc.pendingRequests.delete(id);
1423
1424
  reject(new Error(`MCP request timeout: ${method}`));
@@ -1425,7 +1426,7 @@ function sendRequest(proc, method, params) {
1425
1426
  proc.pendingRequests.set(id, {
1426
1427
  resolve: (v) => {
1427
1428
  clearTimeout(timeout);
1428
- resolve3(v);
1429
+ resolve4(v);
1429
1430
  },
1430
1431
  reject: (e) => {
1431
1432
  clearTimeout(timeout);
@@ -1503,13 +1504,13 @@ async function setSetting(params) {
1503
1504
  await saveSettings(settings);
1504
1505
  }
1505
1506
  function findAvailablePort() {
1506
- return new Promise((resolve3, reject) => {
1507
+ return new Promise((resolve4, reject) => {
1507
1508
  const server = createServer();
1508
1509
  server.listen(0, "127.0.0.1", () => {
1509
1510
  const addr = server.address();
1510
1511
  if (addr && typeof addr === "object") {
1511
1512
  const p = addr.port;
1512
- server.close(() => resolve3(p));
1513
+ server.close(() => resolve4(p));
1513
1514
  } else {
1514
1515
  server.close(() => reject(new Error("Failed to get port")));
1515
1516
  }
@@ -1549,10 +1550,10 @@ async function findOpenClawEntrypoint() {
1549
1550
  ];
1550
1551
  try {
1551
1552
  const cmd = process.platform === "win32" ? "where" : "which";
1552
- const path = await new Promise((resolve3, reject) => {
1553
+ const path = await new Promise((resolve4, reject) => {
1553
1554
  execFile3(cmd, ["openclaw"], (err, stdout) => {
1554
1555
  if (err) reject(err);
1555
- else resolve3(stdout.trim());
1556
+ else resolve4(stdout.trim());
1556
1557
  });
1557
1558
  });
1558
1559
  if (path) candidates.unshift(path);
@@ -1681,7 +1682,7 @@ async function openclawStart(_params) {
1681
1682
  emit("openclaw://status-changed", { status: "crashed" });
1682
1683
  }
1683
1684
  });
1684
- await new Promise((resolve3) => setTimeout(resolve3, 1e3));
1685
+ await new Promise((resolve4) => setTimeout(resolve4, 1e3));
1685
1686
  if (childProcess && !childProcess.killed) {
1686
1687
  processStatus = "running";
1687
1688
  startedAt = Date.now();
@@ -1722,7 +1723,7 @@ async function openclawStop(_params) {
1722
1723
  }
1723
1724
  async function openclawRestart(_params) {
1724
1725
  await openclawStop({});
1725
- await new Promise((resolve3) => setTimeout(resolve3, 500));
1726
+ await new Promise((resolve4) => setTimeout(resolve4, 500));
1726
1727
  await openclawStart({});
1727
1728
  }
1728
1729
  async function openclawStatus(_params) {
@@ -1737,7 +1738,7 @@ async function openclawStatus(_params) {
1737
1738
  async function openclawListChannels(_params) {
1738
1739
  if (processStatus !== "running") return [];
1739
1740
  const entrypoint = await findOpenClawEntrypoint();
1740
- return new Promise((resolve3) => {
1741
+ return new Promise((resolve4) => {
1741
1742
  execFile3(
1742
1743
  "node",
1743
1744
  [entrypoint, "channels", "status", "--json"],
@@ -1745,7 +1746,7 @@ async function openclawListChannels(_params) {
1745
1746
  (err, stdout) => {
1746
1747
  if (err) {
1747
1748
  console.error("[OpenClaw] Failed to list channels:", err);
1748
- resolve3([]);
1749
+ resolve4([]);
1749
1750
  return;
1750
1751
  }
1751
1752
  try {
@@ -1764,9 +1765,9 @@ async function openclawListChannels(_params) {
1764
1765
  }
1765
1766
  channels.length = 0;
1766
1767
  channels.push(...result);
1767
- resolve3(result);
1768
+ resolve4(result);
1768
1769
  } catch {
1769
- resolve3([]);
1770
+ resolve4([]);
1770
1771
  }
1771
1772
  }
1772
1773
  );
@@ -1837,14 +1838,14 @@ async function openclawConnectChannel(params) {
1837
1838
  async function openclawDisconnectChannel(params) {
1838
1839
  const { channelId } = params;
1839
1840
  const entrypoint = await findOpenClawEntrypoint();
1840
- return new Promise((resolve3, reject) => {
1841
+ return new Promise((resolve4, reject) => {
1841
1842
  execFile3(
1842
1843
  "node",
1843
1844
  [entrypoint, "channels", "remove", "--channel", channelId, "--delete"],
1844
1845
  { cwd: OPENCLAW_DIR, timeout: 1e4 },
1845
1846
  (err) => {
1846
1847
  if (err) reject(new Error(`Failed to disconnect: ${err.message}`));
1847
- else resolve3();
1848
+ else resolve4();
1848
1849
  }
1849
1850
  );
1850
1851
  });
@@ -1899,7 +1900,7 @@ async function openclawGrantApproval(params) {
1899
1900
  async function openclawGetQr(params) {
1900
1901
  const { platform: plat } = params;
1901
1902
  const entrypoint = await findOpenClawEntrypoint();
1902
- return new Promise((resolve3, reject) => {
1903
+ return new Promise((resolve4, reject) => {
1903
1904
  execFile3(
1904
1905
  "node",
1905
1906
  [entrypoint, "channels", "qr", "--platform", plat, "--json"],
@@ -1909,9 +1910,9 @@ async function openclawGetQr(params) {
1909
1910
  else {
1910
1911
  try {
1911
1912
  const data = JSON.parse(stdout);
1912
- resolve3(data.qr || data.qrCode || stdout.trim());
1913
+ resolve4(data.qr || data.qrCode || stdout.trim());
1913
1914
  } catch {
1914
- resolve3(stdout.trim());
1915
+ resolve4(stdout.trim());
1915
1916
  }
1916
1917
  }
1917
1918
  }
@@ -1983,11 +1984,93 @@ async function stopWatching() {
1983
1984
  });
1984
1985
  }
1985
1986
 
1987
+ // src/handlers/updater.ts
1988
+ import { spawn as spawn3 } from "child_process";
1989
+ import { platform as platform3, homedir as homedir4 } from "os";
1990
+ import { resolve as resolve3 } from "path";
1991
+ import { createRequire } from "module";
1992
+ var require2 = createRequire(import.meta.url);
1993
+ var pkg = require2("../package.json");
1994
+ var CURRENT_VERSION = pkg.version;
1995
+ var PACKAGE_NAME = "@serendb/serendesktop";
1996
+ var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
1997
+ async function checkForUpdate() {
1998
+ try {
1999
+ const controller = new AbortController();
2000
+ const timeout = setTimeout(() => controller.abort(), 1e4);
2001
+ const res = await fetch(REGISTRY_URL, {
2002
+ signal: controller.signal,
2003
+ headers: { Accept: "application/json" }
2004
+ });
2005
+ clearTimeout(timeout);
2006
+ if (!res.ok) {
2007
+ console.warn(`[Updater] Registry returned ${res.status}`);
2008
+ return { currentVersion: CURRENT_VERSION, latestVersion: null, updateAvailable: false };
2009
+ }
2010
+ const data = await res.json();
2011
+ const latestVersion = data.version ?? null;
2012
+ const updateAvailable = latestVersion !== null && latestVersion !== CURRENT_VERSION && isNewer(latestVersion, CURRENT_VERSION);
2013
+ console.log(`[Updater] Current: ${CURRENT_VERSION}, Latest: ${latestVersion}, Update: ${updateAvailable}`);
2014
+ return { currentVersion: CURRENT_VERSION, latestVersion, updateAvailable };
2015
+ } catch (err) {
2016
+ console.warn("[Updater] Failed to check for updates:", err);
2017
+ return { currentVersion: CURRENT_VERSION, latestVersion: null, updateAvailable: false };
2018
+ }
2019
+ }
2020
+ async function installUpdate() {
2021
+ const home2 = homedir4();
2022
+ const serenDir = resolve3(home2, ".seren-local");
2023
+ const nodeDir = resolve3(serenDir, "node");
2024
+ const binDir = resolve3(serenDir, "bin");
2025
+ const isWin = platform3() === "win32";
2026
+ const nodeBinDir = isWin ? nodeDir : resolve3(nodeDir, "bin");
2027
+ const npmCmd = isWin ? resolve3(nodeBinDir, "npm.cmd") : resolve3(nodeBinDir, "npm");
2028
+ const serendesktopCmd = isWin ? "serendesktop.cmd" : "serendesktop";
2029
+ if (isWin) {
2030
+ const script = [
2031
+ `timeout /t 2 /nobreak >nul`,
2032
+ `set "PATH=${nodeBinDir};${binDir};%PATH%"`,
2033
+ `"${npmCmd}" install -g ${PACKAGE_NAME} --prefix "${serenDir}"`,
2034
+ `"${serendesktopCmd}"`
2035
+ ].join(" && ");
2036
+ const child = spawn3("cmd", ["/c", script], {
2037
+ detached: true,
2038
+ stdio: "ignore",
2039
+ windowsHide: true
2040
+ });
2041
+ child.unref();
2042
+ } else {
2043
+ const script = [
2044
+ `sleep 2`,
2045
+ `export PATH="${nodeBinDir}:${binDir}:$PATH"`,
2046
+ `"${npmCmd}" install -g ${PACKAGE_NAME} --prefix "${serenDir}"`,
2047
+ `"${serendesktopCmd}"`
2048
+ ].join(" && ");
2049
+ const child = spawn3("bash", ["-c", script], {
2050
+ detached: true,
2051
+ stdio: "ignore"
2052
+ });
2053
+ child.unref();
2054
+ }
2055
+ console.log("[Updater] Detached updater spawned, exiting server...");
2056
+ setTimeout(() => process.exit(0), 500);
2057
+ return { started: true };
2058
+ }
2059
+ function isNewer(a, b) {
2060
+ const pa = a.split(".").map(Number);
2061
+ const pb = b.split(".").map(Number);
2062
+ for (let i = 0; i < 3; i++) {
2063
+ if ((pa[i] ?? 0) > (pb[i] ?? 0)) return true;
2064
+ if ((pa[i] ?? 0) < (pb[i] ?? 0)) return false;
2065
+ }
2066
+ return false;
2067
+ }
2068
+
1986
2069
  // src/handlers/wallet.ts
1987
2070
  import { randomBytes as randomBytes2 } from "crypto";
1988
2071
  import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir3 } from "fs/promises";
1989
2072
  import { join as join5 } from "path";
1990
- import { homedir as homedir4 } from "os";
2073
+ import { homedir as homedir5 } from "os";
1991
2074
  import {
1992
2075
  privateKeyToAccount
1993
2076
  } from "viem/accounts";
@@ -2000,7 +2083,7 @@ import {
2000
2083
  getAddress
2001
2084
  } from "viem";
2002
2085
  import { base } from "viem/chains";
2003
- var SEREN_DIR = join5(homedir4(), ".seren-local");
2086
+ var SEREN_DIR = join5(homedir5(), ".seren-local");
2004
2087
  var WALLET_FILE = join5(SEREN_DIR, "data", "crypto-wallet.json");
2005
2088
  var USDC_CONTRACT_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
2006
2089
  var BASE_RPC_URL = "https://mainnet.base.org";
@@ -2316,6 +2399,8 @@ function registerAllHandlers() {
2316
2399
  registerHandler("get_embedding_dimension", getEmbeddingDimension);
2317
2400
  registerHandler("mcp_disconnect", mcpDisconnect);
2318
2401
  registerHandler("mcp_read_resource", mcpReadResource);
2402
+ registerHandler("check_for_update", checkForUpdate);
2403
+ registerHandler("install_update", installUpdate);
2319
2404
  registerHandler("create_conversation", createConversation);
2320
2405
  registerHandler("get_conversations", getConversations);
2321
2406
  registerHandler("get_conversation", getConversation);
@@ -2333,10 +2418,10 @@ import { fileURLToPath } from "url";
2333
2418
  var __dirname = dirname2(fileURLToPath(import.meta.url));
2334
2419
  function getInstalledVersion() {
2335
2420
  try {
2336
- const pkg = JSON.parse(
2421
+ const pkg2 = JSON.parse(
2337
2422
  readFileSync2(join6(__dirname, "..", "package.json"), "utf-8")
2338
2423
  );
2339
- return pkg.version ?? "0.0.0";
2424
+ return pkg2.version ?? "0.0.0";
2340
2425
  } catch {
2341
2426
  return "0.0.0";
2342
2427
  }
@@ -2356,7 +2441,7 @@ async function getLatestVersion() {
2356
2441
  return null;
2357
2442
  }
2358
2443
  }
2359
- function isNewer(latest, current) {
2444
+ function isNewer2(latest, current) {
2360
2445
  const l = latest.split(".").map(Number);
2361
2446
  const c = current.split(".").map(Number);
2362
2447
  for (let i = 0; i < 3; i++) {
@@ -2368,7 +2453,7 @@ function isNewer(latest, current) {
2368
2453
  function checkForUpdates() {
2369
2454
  const current = getInstalledVersion();
2370
2455
  getLatestVersion().then((latest) => {
2371
- if (latest && isNewer(latest, current)) {
2456
+ if (latest && isNewer2(latest, current)) {
2372
2457
  console.log("");
2373
2458
  console.log(` \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557`);
2374
2459
  console.log(` \u2551 Update available: v${current} \u2192 v${latest.padEnd(10)} \u2551`);
@@ -2381,6 +2466,8 @@ function checkForUpdates() {
2381
2466
  }
2382
2467
 
2383
2468
  // src/server.ts
2469
+ var require3 = createRequire2(import.meta.url);
2470
+ var APP_VERSION = require3("../package.json").version;
2384
2471
  var PORT = Number(process.env.SEREN_PORT) || 19420;
2385
2472
  var NO_OPEN = process.argv.includes("--no-open");
2386
2473
  var AUTH_TOKEN = process.env.SEREN_RUNTIME_TOKEN || randomBytes3(32).toString("hex");
@@ -2459,7 +2546,7 @@ function serveSpaFallback(res) {
2459
2546
  return serveHtml(res);
2460
2547
  }
2461
2548
  function openBrowser(url) {
2462
- const cmd = platform3() === "darwin" ? `open "${url}"` : platform3() === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
2549
+ const cmd = platform4() === "darwin" ? `open "${url}"` : platform4() === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
2463
2550
  exec2(cmd, (err) => {
2464
2551
  if (err) console.log(`[Seren Local] Could not open browser: ${err.message}`);
2465
2552
  });
@@ -2534,7 +2621,7 @@ var httpServer = createServer2((req, res) => {
2534
2621
  }
2535
2622
  if (req.url === "/health") {
2536
2623
  res.writeHead(200, { "Content-Type": "application/json" });
2537
- res.end(JSON.stringify({ status: "ok", version: "0.1.0", token: AUTH_TOKEN, buildHash: BUILD_HASH }));
2624
+ res.end(JSON.stringify({ status: "ok", version: APP_VERSION, token: AUTH_TOKEN, buildHash: BUILD_HASH }));
2538
2625
  return;
2539
2626
  }
2540
2627
  const urlPath = req.url || "/";
@@ -2588,7 +2675,7 @@ wss.on("connection", (ws, req) => {
2588
2675
  console.log("[Seren Local] Browser disconnected");
2589
2676
  });
2590
2677
  });
2591
- var dataDir = join7(homedir5(), ".seren-local");
2678
+ var dataDir = join7(homedir6(), ".seren-local");
2592
2679
  mkdirSync2(dataDir, { recursive: true });
2593
2680
  initChatDb(join7(dataDir, "conversations.db"));
2594
2681
  registerAllHandlers();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serendb/serendesktop",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Seren Local runtime — enables ACP agents, local MCP, and file access",
5
5
  "type": "module",
6
6
  "license": "MIT",