@decentnetwork/beagle 0.1.27 → 0.1.28

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,4 +1,4 @@
1
- window.__DK_UI_VERSION="0.1.26";
1
+ window.__DK_UI_VERSION="0.1.27";
2
2
  const ICON_PATHS = {
3
3
  // ---- tab bar (the four must feel like one set) ----
4
4
  users: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
@@ -2593,7 +2593,7 @@ ${peer.address}`
2593
2593
  style: { maxWidth: "100%", maxHeight: "100%", objectFit: "contain", borderRadius: 6 }
2594
2594
  }
2595
2595
  ))),
2596
- pubProfile && /* @__PURE__ */ React.createElement(DkPublicProfile, { T, userid: peer.userId, fallbackName: peer.alias, onClose: () => setPubProfile(false) })
2596
+ pubProfile && /* @__PURE__ */ React.createElement(DkPublicProfile, { T, userid: peer.userId, fallbackName: peer.alias, isFriend: true, onClose: () => setPubProfile(false) })
2597
2597
  );
2598
2598
  }
2599
2599
  function MenuItem({ icon, label, onClick, danger }) {
@@ -2928,8 +2928,13 @@ function DkPunkPicker({ T, onPick, onClose }) {
2928
2928
  /* @__PURE__ */ React.createElement("img", { src: p.image, alt: `punk #${p.id}`, style: { width: "100%", height: "100%", imageRendering: "pixelated", display: "block" } })
2929
2929
  ))))));
2930
2930
  }
2931
- function DkPublicProfile({ T, userid, fallbackName, onClose }) {
2931
+ function DkPublicProfile({ T, userid, fallbackName, onClose, isFriend, isMe }) {
2932
2932
  const [st, setSt] = React.useState({ loading: true });
2933
+ const [addState, setAddState] = React.useState(null);
2934
+ const addFriend = (address) => {
2935
+ setAddState("busy");
2936
+ fetch("/api/add", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ address }) }).then((r) => r.json()).then((r) => setAddState(r.ok !== false ? "sent" : r.error || T.addFailed || "failed")).catch((e) => setAddState(String(e && e.message || e)));
2937
+ };
2933
2938
  React.useEffect(() => {
2934
2939
  let dead = false;
2935
2940
  setSt({ loading: true });
@@ -2960,7 +2965,25 @@ function DkPublicProfile({ T, userid, fallbackName, onClose }) {
2960
2965
  },
2961
2966
  k,
2962
2967
  " \u2197"
2963
- ))), prof.eth && /* @__PURE__ */ React.createElement("div", { style: rowS }, /* @__PURE__ */ React.createElement("span", { style: lbl }, "ethereum"), /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: prof.eth, title: prof.eth }, shortKey(prof.eth, 8, 6))), prof.sol && /* @__PURE__ */ React.createElement("div", { style: rowS }, /* @__PURE__ */ React.createElement("span", { style: lbl }, "solana"), /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: prof.sol, title: prof.sol }, shortKey(prof.sol, 8, 6))), /* @__PURE__ */ React.createElement("div", { style: rowS }, /* @__PURE__ */ React.createElement("span", { style: lbl }, T.userId || "user id"), /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: userid, title: userid }, shortKey(userid, 10, 6))))));
2968
+ ))), prof.eth && /* @__PURE__ */ React.createElement("div", { style: rowS }, /* @__PURE__ */ React.createElement("span", { style: lbl }, "ethereum"), /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: prof.eth, title: prof.eth }, shortKey(prof.eth, 8, 6))), prof.sol && /* @__PURE__ */ React.createElement("div", { style: rowS }, /* @__PURE__ */ React.createElement("span", { style: lbl }, "solana"), /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: prof.sol, title: prof.sol }, shortKey(prof.sol, 8, 6))), /* @__PURE__ */ React.createElement("div", { style: rowS }, /* @__PURE__ */ React.createElement("span", { style: lbl }, T.userId || "user id"), /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: userid, title: userid }, shortKey(userid, 10, 6))), !isMe && !isFriend && prof.address && /* @__PURE__ */ React.createElement("div", { style: { ...rowS, justifyContent: "flex-end" } }, addState === "sent" ? /* @__PURE__ */ React.createElement(Tag, { tone: "accent" }, T.dirSent || "requested") : addState && addState !== "busy" ? /* @__PURE__ */ React.createElement("span", { title: addState, style: { fontFamily: "var(--ui)", fontSize: 11.5, color: "var(--warn, #f59e0b)" } }, T.addFailed || "failed") : /* @__PURE__ */ React.createElement(
2969
+ "button",
2970
+ {
2971
+ onClick: () => addFriend(prof.address),
2972
+ disabled: addState === "busy",
2973
+ style: {
2974
+ padding: "7px 16px",
2975
+ borderRadius: 9,
2976
+ border: "none",
2977
+ cursor: addState === "busy" ? "default" : "pointer",
2978
+ background: "var(--accent)",
2979
+ color: "#fff",
2980
+ fontFamily: "var(--ui)",
2981
+ fontSize: 12.5,
2982
+ opacity: addState === "busy" ? 0.6 : 1
2983
+ }
2984
+ },
2985
+ addState === "busy" ? "\u2026" : T.dirAdd || "Add"
2986
+ )))));
2964
2987
  }
2965
2988
  function DkEnsCard({ T, me, onRecord }) {
2966
2989
  const [st, setSt] = React.useState({ loading: true });
@@ -3309,7 +3332,17 @@ function DiscoverTab({ T, kind, peers, meId, onAdd, onOpenChat }) {
3309
3332
  onOpenChat,
3310
3333
  onOpenProfile: (row) => setProf({ userid: row.userid, name: row.name })
3311
3334
  }
3312
- ))), prof && /* @__PURE__ */ React.createElement(DkPublicProfile, { T, userid: prof.userid, fallbackName: prof.name, onClose: () => setProf(null) }));
3335
+ ))), prof && /* @__PURE__ */ React.createElement(
3336
+ DkPublicProfile,
3337
+ {
3338
+ T,
3339
+ userid: prof.userid,
3340
+ fallbackName: prof.name,
3341
+ onClose: () => setProf(null),
3342
+ isFriend: friendIds.has(prof.userid),
3343
+ isMe: !!meId && prof.userid === meId
3344
+ }
3345
+ ));
3313
3346
  }
3314
3347
  const DK_FILE_RTC_KIND = "file";
3315
3348
  const DK_FILE_RTC_CHUNK = 16 * 1024;
@@ -4254,10 +4287,13 @@ const STR = {
4254
4287
  updRestarting: "Restarting\u2026 this page reloads when it\u2019s back. (If beagle was started by hand in a terminal, run it again.)",
4255
4288
  updNpx: "Running via npx \u2014 restarting picks up the latest version automatically.",
4256
4289
  updDev: "Running from a source checkout \u2014 update with:",
4257
- updLanNote: "agentnet (lan) updates separately; the daemon restart is manual:",
4290
+ updLanNote: "agentnet (lan): one click reinstalls it and asks the running daemon to restart itself. Manual equivalent:",
4258
4291
  updCheck: "check for updates",
4259
4292
  updCheckLabel: "updates",
4260
4293
  updLatest: "up to date \u2713",
4294
+ updLanNow: "Update lan",
4295
+ updLanDone: "lan updated \u2014 daemon restarted with the new version \u2713",
4296
+ updLanNoRestart: "lan updated, but the daemon was not running (or refused) \u2014 the new version applies on its next start.",
4261
4297
  pubOpen: "View public profile",
4262
4298
  pubNone: "No public profile \u2014 this user has not registered a beagles.eth name.",
4263
4299
  ensNoEthWallet: "no Ethereum wallet extension found (MetaMask\u2026)",
@@ -4413,10 +4449,13 @@ const STR = {
4413
4449
  updRestarting: "\u6B63\u5728\u91CD\u542F\u2026\u5B8C\u6210\u540E\u672C\u9875\u4F1A\u81EA\u52A8\u5237\u65B0\u3002(\u5982\u679C beagle \u662F\u5728\u7EC8\u7AEF\u91CC\u624B\u52A8\u542F\u52A8\u7684,\u8BF7\u91CD\u65B0\u8FD0\u884C\u4E00\u6B21\u3002)",
4414
4450
  updNpx: "\u4F60\u662F\u7528 npx \u8FD0\u884C\u7684\u2014\u2014\u91CD\u542F\u5373\u81EA\u52A8\u83B7\u53D6\u6700\u65B0\u7248\u3002",
4415
4451
  updDev: "\u5F53\u524D\u4ECE\u6E90\u7801\u76EE\u5F55\u8FD0\u884C,\u8BF7\u624B\u52A8\u66F4\u65B0:",
4416
- updLanNote: "agentnet(lan)\u9700\u5355\u72EC\u5347\u7EA7,daemon \u91CD\u542F\u9700\u624B\u52A8\u6267\u884C:",
4452
+ updLanNote: "agentnet(lan):\u4E00\u952E\u91CD\u88C5\u5E76\u8BA9\u8FD0\u884C\u4E2D\u7684 daemon \u81EA\u6211\u91CD\u542F\u3002\u7B49\u4EF7\u624B\u52A8\u547D\u4EE4:",
4417
4453
  updCheck: "\u68C0\u67E5\u66F4\u65B0",
4418
4454
  updCheckLabel: "\u66F4\u65B0",
4419
4455
  updLatest: "\u5DF2\u662F\u6700\u65B0\u7248\u672C \u2713",
4456
+ updLanNow: "\u66F4\u65B0 lan",
4457
+ updLanDone: "lan \u5DF2\u66F4\u65B0\u2014\u2014daemon \u5DF2\u7528\u65B0\u7248\u91CD\u542F \u2713",
4458
+ updLanNoRestart: "lan \u5DF2\u66F4\u65B0,\u4F46 daemon \u672A\u5728\u8FD0\u884C(\u6216\u91CD\u542F\u88AB\u62D2)\u2014\u2014\u4E0B\u6B21\u542F\u52A8\u65F6\u751F\u6548\u3002",
4420
4459
  pubOpen: "\u67E5\u770B\u516C\u5F00\u8D44\u6599",
4421
4460
  pubNone: "\u6682\u65E0\u516C\u5F00\u8D44\u6599\u2014\u2014\u5BF9\u65B9\u8FD8\u6CA1\u6709\u6CE8\u518C beagles.eth \u540D\u5B57\u3002",
4422
4461
  ensNoEthWallet: "\u672A\u68C0\u6D4B\u5230\u4EE5\u592A\u574A\u94B1\u5305\u63D2\u4EF6(MetaMask \u7B49)",
@@ -4476,7 +4515,7 @@ function DkUpdateModal({ T, info, onClose }) {
4476
4515
  const doUpdate = () => {
4477
4516
  setPhase("running");
4478
4517
  setErr(null);
4479
- fetch("/api/update-run", { method: "POST" }).then((r) => r.json()).then((d) => {
4518
+ fetch("/api/update-run", { method: "POST", headers: { "content-type": "application/json" }, body: "{}" }).then((r) => r.json()).then((d) => {
4480
4519
  if (d.ok)
4481
4520
  setPhase("done");
4482
4521
  else {
@@ -4488,6 +4527,21 @@ function DkUpdateModal({ T, info, onClose }) {
4488
4527
  setErr(String(e && e.message || e));
4489
4528
  });
4490
4529
  };
4530
+ const doLanUpdate = () => {
4531
+ setPhase("running");
4532
+ setErr(null);
4533
+ fetch("/api/update-run", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ pkg: "lan" }) }).then((r) => r.json()).then((d) => {
4534
+ if (d.ok)
4535
+ setPhase(d.restarted ? "lan-done" : "lan-norestart");
4536
+ else {
4537
+ setPhase("error");
4538
+ setErr(d.error || "update failed");
4539
+ }
4540
+ }).catch((e) => {
4541
+ setPhase("error");
4542
+ setErr(String(e && e.message || e));
4543
+ });
4544
+ };
4491
4545
  const doRestart = () => {
4492
4546
  setPhase("restarting");
4493
4547
  fetch("/api/update-restart", { method: "POST" }).catch(() => {
@@ -4512,7 +4566,7 @@ function DkUpdateModal({ T, info, onClose }) {
4512
4566
  background: tone ? "var(--accent)" : "transparent",
4513
4567
  color: tone ? "#fff" : "var(--text)"
4514
4568
  } }, label);
4515
- return /* @__PURE__ */ React.createElement("div", { style: { position: "fixed", inset: 0, zIndex: 95, background: "color-mix(in oklab, #000, transparent 38%)", display: "flex", alignItems: "center", justifyContent: "center", padding: 24 } }, /* @__PURE__ */ React.createElement("div", { style: { width: 430, maxWidth: "94vw", background: "var(--panel)", border: "1px solid var(--line)", borderRadius: 16, padding: "20px 22px", display: "flex", flexDirection: "column", gap: 14 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 13.5, fontWeight: 700, color: "var(--text)" } }, T.updTitle || "Update available"), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 6 } }, behind.map((u) => /* @__PURE__ */ React.createElement("div", { key: u.pkg, style: { display: "flex", alignItems: "center", gap: 10, fontFamily: "var(--mono)", fontSize: 12.5 } }, /* @__PURE__ */ React.createElement("span", { style: { width: 70, color: "var(--faint)" } }, short(u.pkg)), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--dim)" } }, u.current), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--faint)" } }, "\u2192"), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--accent)", fontWeight: 700 } }, u.latest)))), phase === "idle" && appBehind && info.mode === "dev" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--dim)", lineHeight: 1.5 } }, T.updDev || "Running from a source checkout \u2014 update with:", /* @__PURE__ */ React.createElement("br", null), /* @__PURE__ */ React.createElement("code", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--text)" } }, "git pull && npm run build")), phase === "idle" && appBehind && info.mode === "npx" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--dim)", lineHeight: 1.5 } }, T.updNpx || "Running via npx \u2014 restarting picks up the latest version automatically."), lanBehind && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--dim)", lineHeight: 1.5 } }, T.updLanNote || "agentnet (lan) updates separately; the daemon restart is manual:", /* @__PURE__ */ React.createElement("br", null), /* @__PURE__ */ React.createElement("code", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--text)", userSelect: "all" } }, "npm i -g @decentnetwork/lan@latest && agentnet restart")), phase === "running" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--dim)" } }, T.updBusy || "Updating\u2026 (about a minute)"), phase === "done" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--online)" } }, T.updDone || "Updated \u2014 restart to apply."), phase === "restarting" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--dim)" } }, T.updRestarting || "Restarting\u2026 this page reloads when it\u2019s back. (If beagle was started by hand in a terminal, run it again.)"), phase === "error" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--danger)", wordBreak: "break-word" } }, err), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", justifyContent: "flex-end", gap: 8 } }, (phase === "idle" || phase === "error") && btn(T.updLater || "Later", onClose), phase === "idle" && appBehind && info.mode === "global" && btn(T.updNow || "Update now", doUpdate, true), phase === "idle" && appBehind && info.mode === "npx" && btn(T.updRestart || "Restart", doRestart, true), phase === "done" && btn(T.updRestart || "Restart", doRestart, true))));
4569
+ return /* @__PURE__ */ React.createElement("div", { style: { position: "fixed", inset: 0, zIndex: 95, background: "color-mix(in oklab, #000, transparent 38%)", display: "flex", alignItems: "center", justifyContent: "center", padding: 24 } }, /* @__PURE__ */ React.createElement("div", { style: { width: 430, maxWidth: "94vw", background: "var(--panel)", border: "1px solid var(--line)", borderRadius: 16, padding: "20px 22px", display: "flex", flexDirection: "column", gap: 14 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 13.5, fontWeight: 700, color: "var(--text)" } }, T.updTitle || "Update available"), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 6 } }, behind.map((u) => /* @__PURE__ */ React.createElement("div", { key: u.pkg, style: { display: "flex", alignItems: "center", gap: 10, fontFamily: "var(--mono)", fontSize: 12.5 } }, /* @__PURE__ */ React.createElement("span", { style: { width: 70, color: "var(--faint)" } }, short(u.pkg)), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--dim)" } }, u.current), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--faint)" } }, "\u2192"), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--accent)", fontWeight: 700 } }, u.latest)))), phase === "idle" && appBehind && info.mode === "dev" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--dim)", lineHeight: 1.5 } }, T.updDev || "Running from a source checkout \u2014 update with:", /* @__PURE__ */ React.createElement("br", null), /* @__PURE__ */ React.createElement("code", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--text)" } }, "git pull && npm run build")), phase === "idle" && appBehind && info.mode === "npx" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--dim)", lineHeight: 1.5 } }, T.updNpx || "Running via npx \u2014 restarting picks up the latest version automatically."), lanBehind && phase === "idle" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--dim)", lineHeight: 1.5 } }, T.updLanNote || "agentnet (lan): one click reinstalls it and asks the running daemon to restart itself. Manual equivalent:", /* @__PURE__ */ React.createElement("br", null), /* @__PURE__ */ React.createElement("code", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--text)", userSelect: "all" } }, "npm i -g @decentnetwork/lan@latest && agentnet restart")), phase === "lan-done" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--online)" } }, T.updLanDone || "lan updated \u2014 daemon restarted with the new version \u2713"), phase === "lan-norestart" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--warn, #f59e0b)" } }, T.updLanNoRestart || "lan updated, but the daemon was not running (or refused) \u2014 the new version applies on its next start."), phase === "running" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--dim)" } }, T.updBusy || "Updating\u2026 (about a minute)"), phase === "done" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--online)" } }, T.updDone || "Updated \u2014 restart to apply."), phase === "restarting" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--dim)" } }, T.updRestarting || "Restarting\u2026 this page reloads when it\u2019s back. (If beagle was started by hand in a terminal, run it again.)"), phase === "error" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--danger)", wordBreak: "break-word" } }, err), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", justifyContent: "flex-end", gap: 8 } }, (phase === "idle" || phase === "error" || phase === "lan-done" || phase === "lan-norestart") && btn(phase === "idle" ? T.updLater || "Later" : T.receivedClose || "Close", onClose), phase === "idle" && lanBehind && btn(T.updLanNow || "Update lan", doLanUpdate, !appBehind), phase === "idle" && appBehind && info.mode === "global" && btn(T.updNow || "Update now", doUpdate, true), phase === "idle" && appBehind && info.mode === "npx" && btn(T.updRestart || "Restart", doRestart, true), phase === "done" && btn(T.updRestart || "Restart", doRestart, true))));
4516
4570
  }
4517
4571
  function DkApp() {
4518
4572
  const [t, setTweak] = useTweaks(DK_DEFAULTS);
package/dist/server.js CHANGED
@@ -158,6 +158,8 @@ function ensPubFromEntry(ens, v) {
158
158
  const t = v?.texts ?? {};
159
159
  return {
160
160
  ens,
161
+ // Shareable Carrier address — lets the profile card offer add-friend.
162
+ address: t.carrierAddress || null,
161
163
  avatarUrl: t.avatar || null,
162
164
  punkId: v?.nft === "CryptoPunks" && typeof v?.nftid === "number" && v.nftid > 0 ? v.nftid : null,
163
165
  displayName: t.displayName || null,
@@ -241,12 +243,12 @@ function verLt(a, b) {
241
243
  }
242
244
  return false;
243
245
  }
244
- /** Run `npm i -g @decentnetwork/beagle@latest`. npm is found on PATH, else via
245
- * npm-cli.js next to the running node (covers pm2/cron environments whose
246
- * PATH lacks the nvm shims). */
247
- async function npmSelfUpdate() {
246
+ /** Run `npm i -g <pkg>@latest`. npm is found on PATH, else via npm-cli.js
247
+ * next to the running node (covers pm2/cron environments whose PATH lacks
248
+ * the nvm shims). */
249
+ async function npmGlobalInstall(pkg) {
248
250
  const { spawn } = await import("node:child_process");
249
- const args = ["i", "-g", `${BEAGLE_PKG}@latest`];
251
+ const args = ["i", "-g", `${pkg}@latest`];
250
252
  const npmCli = join(dirname(process.execPath), "..", "lib", "node_modules", "npm", "bin", "npm-cli.js");
251
253
  const attempts = [
252
254
  { cmd: "npm", args },
@@ -268,11 +270,40 @@ async function npmSelfUpdate() {
268
270
  if (r.code === 0)
269
271
  return { ok: true, log: tail };
270
272
  const hint = /EACCES|permission denied/i.test(r.out)
271
- ? " — no write access to the global npm dir; run `sudo npm i -g @decentnetwork/beagle@latest` manually"
273
+ ? ` — no write access to the global npm dir; run \`sudo npm i -g ${pkg}@latest\` manually`
272
274
  : "";
273
275
  return { ok: false, error: `npm exited ${r.code}${hint}`, log: tail };
274
276
  }
275
- return { ok: false, error: "npm not found — update manually: npm i -g @decentnetwork/beagle@latest" };
277
+ return { ok: false, error: `npm not found — update manually: npm i -g ${pkg}@latest` };
278
+ }
279
+ /** Ask the running agentnet daemon to re-exec itself so it picks up the
280
+ * just-installed lan version. Tries the PATH binary, then the global
281
+ * install's CLI entry directly (pm2 PATH often lacks the nvm shims). */
282
+ async function agentnetRestart() {
283
+ const { spawn } = await import("node:child_process");
284
+ const cliJs = join(dirname(process.execPath), "..", "lib", "node_modules", "@decentnetwork", "lan", "dist", "cli", "index.js");
285
+ const attempts = [
286
+ { cmd: "agentnet", args: ["restart"] },
287
+ ...(existsSync(cliJs) ? [{ cmd: process.execPath, args: [cliJs, "restart"] }] : []),
288
+ ];
289
+ let lastErr = "agentnet CLI not found";
290
+ for (const a of attempts) {
291
+ const r = await new Promise((resolveRun) => {
292
+ const ch = spawn(a.cmd, a.args, { shell: process.platform === "win32" });
293
+ let out = "";
294
+ const timer = setTimeout(() => ch.kill("SIGKILL"), 60_000);
295
+ ch.stdout?.on("data", (c) => (out += c));
296
+ ch.stderr?.on("data", (c) => (out += c));
297
+ ch.on("error", (e) => { clearTimeout(timer); resolveRun({ code: null, out, spawnErr: e.message }); });
298
+ ch.on("close", (code) => { clearTimeout(timer); resolveRun({ code, out }); });
299
+ });
300
+ if (r.spawnErr)
301
+ continue;
302
+ if (r.code === 0)
303
+ return { ok: true };
304
+ lastErr = r.out.slice(-400) || `agentnet restart exited ${r.code}`;
305
+ }
306
+ return { ok: false, error: lastErr };
276
307
  }
277
308
  /** True only when the request came from the local machine. Used to gate the
278
309
  * "Sign in with Decent" routes so binding the UI to a LAN IP can't expose
@@ -1251,6 +1282,20 @@ export function startBeagleServer(opts) {
1251
1282
  sendJson(res, 403, { ok: false, error: "update runs only from this machine (localhost)" });
1252
1283
  return;
1253
1284
  }
1285
+ const body = await readBody(req);
1286
+ // lan (the agentnet daemon) updates independently of beagle's own
1287
+ // install mode: global reinstall, then ask the daemon to re-exec.
1288
+ if (body.pkg === "lan") {
1289
+ const inst = await npmGlobalInstall("@decentnetwork/lan");
1290
+ if (!inst.ok) {
1291
+ sendJson(res, 502, inst);
1292
+ return;
1293
+ }
1294
+ const rr = await agentnetRestart();
1295
+ updateCache = null;
1296
+ sendJson(res, 200, { ok: true, restarted: rr.ok, ...(rr.ok ? {} : { note: rr.error }) });
1297
+ return;
1298
+ }
1254
1299
  const mode = installMode();
1255
1300
  if (mode === "dev") {
1256
1301
  sendJson(res, 400, { ok: false, error: "running from a source checkout — update with: git pull && npm run build" });
@@ -1261,7 +1306,7 @@ export function startBeagleServer(opts) {
1261
1306
  sendJson(res, 200, { ok: true, npx: true });
1262
1307
  return;
1263
1308
  }
1264
- const r = await npmSelfUpdate();
1309
+ const r = await npmGlobalInstall(BEAGLE_PKG);
1265
1310
  updateCache = null; // next check reflects the new install
1266
1311
  sendJson(res, r.ok ? 200 : 502, r);
1267
1312
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/beagle",
3
- "version": "0.1.27",
3
+ "version": "0.1.28",
4
4
  "description": "Beagle — P2P chat, file transfer and calls for regular users, on the Decent Network. No admin privilege required.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",