@decentnetwork/beagle 0.1.26 → 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.25";
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 });
@@ -3146,6 +3169,22 @@ function DkEnsCard({ T, me, onRecord }) {
3146
3169
  }
3147
3170
  ), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--dim)", flexShrink: 0 } }, ".beagles.eth"), /* @__PURE__ */ React.createElement(Btn, { tone: "accent", size: "sm", disabled: busy || !label.trim(), onClick: register }, busy ? "\u2026" : st.registered && st.mineOwned ? T.ensUpdate || "update" : T.ensRegister || "register")), st.registered && !st.mineOwned && /* @__PURE__ */ React.createElement("div", { style: { padding: "10px 16px", borderBottom: "1px solid var(--line)", fontFamily: "var(--ui)", fontSize: 11.5, color: "var(--faint)" } }, (T.ensWalletOwned || "registered via your mobile wallet as") + " ", /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)" } }, rec && rec.name)), st.registered && /* @__PURE__ */ React.createElement("div", { style: row }, /* @__PURE__ */ React.createElement("span", { style: { width: 130, flexShrink: 0, fontFamily: "var(--mono)", fontSize: 11.5, fontWeight: 600, color: "var(--faint)", textTransform: "uppercase", letterSpacing: 0.5 } }, T.ensAvatar || "avatar"), upAvatar ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("img", { src: upAvatar, alt: "", width: 34, height: 34, style: { width: 34, height: 34, borderRadius: 9, objectFit: "cover", flexShrink: 0, background: "var(--panel-2)" } }), /* @__PURE__ */ React.createElement("span", { style: { flex: 1, minWidth: 0, fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--text)" } }, T.ensCustom || "custom image"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", tone: "danger", disabled: busy || !st.mineOwned, onClick: () => post("/api/ens-avatar", { nftid: null }) }, T.ensClear || "clear")) : punkId != null ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(DkPunkAvatar, { id: punkId, size: 34, radius: 9, fallbackSeed: me.userId }), /* @__PURE__ */ React.createElement("span", { style: { flex: 1, minWidth: 0, fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--text)" } }, `CryptoPunk #${punkId}`), /* @__PURE__ */ React.createElement(Btn, { size: "sm", tone: "danger", disabled: busy || !st.mineOwned, onClick: () => post("/api/ens-avatar", { nftid: null }) }, T.ensClear || "clear")) : /* @__PURE__ */ React.createElement("span", { style: { flex: 1, fontFamily: "var(--ui)", fontSize: 12, color: "var(--faint)" } }, T.ensNotSet || "not set"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", disabled: busy || !st.mineOwned, onClick: () => fileRef.current && fileRef.current.click() }, T.ensUpload || "upload"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", disabled: busy || !st.mineOwned, onClick: () => setPicking(true) }, T.ensChoose || "choose"), /* @__PURE__ */ React.createElement("input", { ref: fileRef, type: "file", accept: "image/*", onChange: onUploadFile, style: { display: "none" } })), walletRow(T.ensEth || "ethereum", "eth", boundEth, bindEth, false), walletRow(T.ensSol || "solana", "sol", boundSol, bindSol, !msg), msg && /* @__PURE__ */ React.createElement("div", { style: { padding: "10px 16px", fontFamily: "var(--ui)", fontSize: 12, color: msg.tone === "ok" ? "var(--online)" : "var(--danger)" } }, msg.text)), picking && /* @__PURE__ */ React.createElement(DkPunkPicker, { T, onPick: pickPunk, onClose: () => setPicking(false) })), !st.loading && st.registered && /* @__PURE__ */ React.createElement(Card, { label: T.linksCard || "Links" }, SOCIALS.map((s, i) => socialRow(s, i === SOCIALS.length - 1))));
3148
3171
  }
3172
+ function DkUpdateCheck({ T }) {
3173
+ const [st, setSt] = React.useState("idle");
3174
+ const [info, setInfo] = React.useState(null);
3175
+ const check = () => {
3176
+ setSt("busy");
3177
+ fetch("/api/update-check?force=1").then((r) => r.json()).then((d) => {
3178
+ const behind = (d && d.updates || []).filter((u) => u.behind);
3179
+ if (d && d.ok && behind.length) {
3180
+ setInfo(d);
3181
+ setSt("idle");
3182
+ } else
3183
+ setSt("latest");
3184
+ }).catch(() => setSt("idle"));
3185
+ };
3186
+ return /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 14, padding: "13px 16px" } }, /* @__PURE__ */ React.createElement("span", { style: { width: 130, flexShrink: 0, fontFamily: "var(--mono)", fontSize: 11.5, fontWeight: 600, color: "var(--faint)", textTransform: "uppercase", letterSpacing: 0.5 } }, T.updCheckLabel || "updates"), /* @__PURE__ */ React.createElement("span", { style: { flex: 1, fontFamily: "var(--ui)", fontSize: 12, color: st === "latest" ? "var(--online)" : "var(--faint)" } }, st === "latest" ? T.updLatest || "up to date \u2713" : ""), /* @__PURE__ */ React.createElement(Btn, { size: "sm", disabled: st === "busy", onClick: check }, st === "busy" ? "\u2026" : T.updCheck || "check for updates"), info && /* @__PURE__ */ React.createElement(DkUpdateModal, { T, info, onClose: () => setInfo(null) }));
3187
+ }
3149
3188
  function ProfileTab({ T, me, onEdit }) {
3150
3189
  const [qr, setQr] = React.useState(null);
3151
3190
  const [editing, setEditing] = React.useState(false);
@@ -3164,7 +3203,7 @@ function ProfileTab({ T, me, onEdit }) {
3164
3203
  },
3165
3204
  style: { width: 16, height: 16, accentColor: "var(--accent)" }
3166
3205
  }
3167
- ), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13.5, fontWeight: 600, color: "var(--text)" } }, T && T.autoAcceptLabel || "Auto-accept friend requests"), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 11.5, color: "var(--dim)", marginTop: 2 } }, T && T.autoAcceptSub || "Off: requests wait in the sidebar for your approval.")))), /* @__PURE__ */ React.createElement(Card, { label: T.network }, /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.virtualIp, value: me.ip, copy: true }), /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.wireLabel, value: `${me.wire} \xB7 lossless`, mono: false }), /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.version, value: `lan ${me.lanVer} \xB7 peer ${me.peerVer} \xB7 ${me.channel}`, last: true })), /* @__PURE__ */ React.createElement(Card, { label: T.dangerZone }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 13, padding: "14px 16px" } }, /* @__PURE__ */ React.createElement("div", { style: { width: 34, height: 34, borderRadius: 8, flexShrink: 0, background: "color-mix(in oklab, var(--danger), transparent 86%)", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--danger)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "trash", size: 17, stroke: 2 })), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13.5, fontWeight: 600, color: "var(--danger)" } }, T.deleteNode), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--faint)", marginTop: 1 } }, T.deleteSub)), /* @__PURE__ */ React.createElement(Btn, { tone: "danger", size: "sm" }, T.delete)))), qr && /* @__PURE__ */ React.createElement(DkQrModal, { T, value: qr.value, label: qr.label, onClose: () => setQr(null) }), editing && /* @__PURE__ */ React.createElement(DkEditModal, { T, me, onClose: () => setEditing(false), onSave: (name, description) => {
3206
+ ), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13.5, fontWeight: 600, color: "var(--text)" } }, T && T.autoAcceptLabel || "Auto-accept friend requests"), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 11.5, color: "var(--dim)", marginTop: 2 } }, T && T.autoAcceptSub || "Off: requests wait in the sidebar for your approval.")))), /* @__PURE__ */ React.createElement(Card, { label: T.network }, /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.virtualIp, value: me.ip, copy: true }), /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.wireLabel, value: `${me.wire} \xB7 lossless`, mono: false }), /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.version, value: `lan ${me.lanVer} \xB7 peer ${me.peerVer} \xB7 ${me.channel}` }), /* @__PURE__ */ React.createElement(DkUpdateCheck, { T })), /* @__PURE__ */ React.createElement(Card, { label: T.dangerZone }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 13, padding: "14px 16px" } }, /* @__PURE__ */ React.createElement("div", { style: { width: 34, height: 34, borderRadius: 8, flexShrink: 0, background: "color-mix(in oklab, var(--danger), transparent 86%)", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--danger)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "trash", size: 17, stroke: 2 })), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13.5, fontWeight: 600, color: "var(--danger)" } }, T.deleteNode), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--faint)", marginTop: 1 } }, T.deleteSub)), /* @__PURE__ */ React.createElement(Btn, { tone: "danger", size: "sm" }, T.delete)))), qr && /* @__PURE__ */ React.createElement(DkQrModal, { T, value: qr.value, label: qr.label, onClose: () => setQr(null) }), editing && /* @__PURE__ */ React.createElement(DkEditModal, { T, me, onClose: () => setEditing(false), onSave: (name, description) => {
3168
3207
  if (onEdit)
3169
3208
  onEdit(name, description);
3170
3209
  setEditing(false);
@@ -3293,7 +3332,17 @@ function DiscoverTab({ T, kind, peers, meId, onAdd, onOpenChat }) {
3293
3332
  onOpenChat,
3294
3333
  onOpenProfile: (row) => setProf({ userid: row.userid, name: row.name })
3295
3334
  }
3296
- ))), 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
+ ));
3297
3346
  }
3298
3347
  const DK_FILE_RTC_KIND = "file";
3299
3348
  const DK_FILE_RTC_CHUNK = 16 * 1024;
@@ -4238,7 +4287,13 @@ const STR = {
4238
4287
  updRestarting: "Restarting\u2026 this page reloads when it\u2019s back. (If beagle was started by hand in a terminal, run it again.)",
4239
4288
  updNpx: "Running via npx \u2014 restarting picks up the latest version automatically.",
4240
4289
  updDev: "Running from a source checkout \u2014 update with:",
4241
- 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:",
4291
+ updCheck: "check for updates",
4292
+ updCheckLabel: "updates",
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.",
4242
4297
  pubOpen: "View public profile",
4243
4298
  pubNone: "No public profile \u2014 this user has not registered a beagles.eth name.",
4244
4299
  ensNoEthWallet: "no Ethereum wallet extension found (MetaMask\u2026)",
@@ -4394,7 +4449,13 @@ const STR = {
4394
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)",
4395
4450
  updNpx: "\u4F60\u662F\u7528 npx \u8FD0\u884C\u7684\u2014\u2014\u91CD\u542F\u5373\u81EA\u52A8\u83B7\u53D6\u6700\u65B0\u7248\u3002",
4396
4451
  updDev: "\u5F53\u524D\u4ECE\u6E90\u7801\u76EE\u5F55\u8FD0\u884C,\u8BF7\u624B\u52A8\u66F4\u65B0:",
4397
- 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:",
4453
+ updCheck: "\u68C0\u67E5\u66F4\u65B0",
4454
+ updCheckLabel: "\u66F4\u65B0",
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",
4398
4459
  pubOpen: "\u67E5\u770B\u516C\u5F00\u8D44\u6599",
4399
4460
  pubNone: "\u6682\u65E0\u516C\u5F00\u8D44\u6599\u2014\u2014\u5BF9\u65B9\u8FD8\u6CA1\u6709\u6CE8\u518C beagles.eth \u540D\u5B57\u3002",
4400
4461
  ensNoEthWallet: "\u672A\u68C0\u6D4B\u5230\u4EE5\u592A\u574A\u94B1\u5305\u63D2\u4EF6(MetaMask \u7B49)",
@@ -4454,7 +4515,7 @@ function DkUpdateModal({ T, info, onClose }) {
4454
4515
  const doUpdate = () => {
4455
4516
  setPhase("running");
4456
4517
  setErr(null);
4457
- 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) => {
4458
4519
  if (d.ok)
4459
4520
  setPhase("done");
4460
4521
  else {
@@ -4466,6 +4527,21 @@ function DkUpdateModal({ T, info, onClose }) {
4466
4527
  setErr(String(e && e.message || e));
4467
4528
  });
4468
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
+ };
4469
4545
  const doRestart = () => {
4470
4546
  setPhase("restarting");
4471
4547
  fetch("/api/update-restart", { method: "POST" }).catch(() => {
@@ -4490,7 +4566,7 @@ function DkUpdateModal({ T, info, onClose }) {
4490
4566
  background: tone ? "var(--accent)" : "transparent",
4491
4567
  color: tone ? "#fff" : "var(--text)"
4492
4568
  } }, label);
4493
- 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))));
4494
4570
  }
4495
4571
  function DkApp() {
4496
4572
  const [t, setTweak] = useTweaks(DK_DEFAULTS);
@@ -4748,4 +4824,5 @@ function DkApp() {
4748
4824
  }
4749
4825
  ));
4750
4826
  }
4827
+ Object.assign(window, { DkUpdateModal });
4751
4828
  ReactDOM.createRoot(document.getElementById("root")).render(/* @__PURE__ */ React.createElement(DkApp, null));
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.26",
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",