@decentnetwork/beagle 0.1.110 → 0.1.112
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 +1 -0
- package/dist/desktop/app.js +122 -28
- package/dist/server.d.ts +4 -0
- package/dist/server.js +104 -0
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -172,6 +172,7 @@ async function main() {
|
|
|
172
172
|
doraRosterPath,
|
|
173
173
|
downloadsDir: resolve(args.configDir, "downloads"),
|
|
174
174
|
messagesPath: resolve(args.configDir, "messages.json"),
|
|
175
|
+
servicePeersPath: resolve(args.configDir, "service-peers-local.json"),
|
|
175
176
|
callIce: {
|
|
176
177
|
keyFile: resolve(decentlanCarrierDir(args.configDir), "keypair.json"),
|
|
177
178
|
bootstrapNodes,
|
package/dist/desktop/app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
window.__DK_UI_VERSION="0.1.
|
|
1
|
+
window.__DK_UI_VERSION="0.1.112";
|
|
2
2
|
"use strict";
|
|
3
3
|
(() => {
|
|
4
4
|
// packages/beagle-ui/dist/beagle-ui.js
|
|
@@ -644,6 +644,18 @@ window.__DK_UI_VERSION="0.1.110";
|
|
|
644
644
|
confirm: "list",
|
|
645
645
|
scope: "assistant"
|
|
646
646
|
},
|
|
647
|
+
{
|
|
648
|
+
name: "nodes-refresh",
|
|
649
|
+
usage: "/nodes refresh",
|
|
650
|
+
summary: "Look for friends that are provably service peers (the shipped exit table, this machine's dora roster) and offer to reclassify them. Nothing changes until you tick and confirm.",
|
|
651
|
+
params: {
|
|
652
|
+
type: "object",
|
|
653
|
+
properties: {},
|
|
654
|
+
required: []
|
|
655
|
+
},
|
|
656
|
+
confirm: "list",
|
|
657
|
+
scope: "assistant"
|
|
658
|
+
},
|
|
647
659
|
{
|
|
648
660
|
name: "find",
|
|
649
661
|
usage: "/find <text>",
|
|
@@ -818,7 +830,8 @@ window.__DK_UI_VERSION="0.1.110";
|
|
|
818
830
|
file: "service-peers.json",
|
|
819
831
|
loader: "loadServicePeers()",
|
|
820
832
|
rule: "A peer is a service peer if and only if its userid is in this list. The Assistant never infers it from a name or from having no messages. /friends excludes them entirely; /nodes is only them.",
|
|
821
|
-
later: "A dora / ENS / bridge / directory service returns the same shape; the bundled file is v1."
|
|
833
|
+
later: "A dora / ENS / bridge / directory service returns the same shape; the bundled file is v1.",
|
|
834
|
+
refresh: "/nodes refresh asks GET /api/assistant/service-candidates for evidence-backed candidates and writes the ticked ones to this machine's overlay (POST /api/assistant/service-peers, stored at <configDir>/service-peers-local.json). The shipped list is never edited at runtime; the overlay wins on the same userid."
|
|
822
835
|
}
|
|
823
836
|
};
|
|
824
837
|
var service_peers_default = {
|
|
@@ -973,6 +986,8 @@ window.__DK_UI_VERSION="0.1.110";
|
|
|
973
986
|
const sub = (parts[1] || "").toLowerCase();
|
|
974
987
|
if (sub === "list" || sub === "remove")
|
|
975
988
|
return { ok: true, name: `${head}-${sub}`, args: parts.slice(2) };
|
|
989
|
+
if (head === "nodes" && sub === "refresh")
|
|
990
|
+
return { ok: true, name: "nodes-refresh", args: parts.slice(2) };
|
|
976
991
|
return { ok: false, name: head, args: parts.slice(1), error: `${head}-subcommand` };
|
|
977
992
|
}
|
|
978
993
|
return { ok: true, name: head, args: parts.slice(1) };
|
|
@@ -1010,6 +1025,19 @@ window.__DK_UI_VERSION="0.1.110";
|
|
|
1010
1025
|
}
|
|
1011
1026
|
return map;
|
|
1012
1027
|
}
|
|
1028
|
+
function mergeServicePeers(bundled, local) {
|
|
1029
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1030
|
+
for (const p of bundled && bundled.peers || [])
|
|
1031
|
+
if (p && p.userid)
|
|
1032
|
+
byId.set(p.userid, { ...p, source: "app" });
|
|
1033
|
+
for (const p of local && local.peers || [])
|
|
1034
|
+
if (p && p.userid)
|
|
1035
|
+
byId.set(p.userid, { ...p, source: "local" });
|
|
1036
|
+
return { version: 1, source: "merged", peers: [...byId.values()] };
|
|
1037
|
+
}
|
|
1038
|
+
function newCandidates(candidates, svcMap) {
|
|
1039
|
+
return (candidates != null ? candidates : []).filter((c) => c && c.userid && !(svcMap && svcMap.has(c.userid)));
|
|
1040
|
+
}
|
|
1013
1041
|
function partitionPeers(friends, svcMap) {
|
|
1014
1042
|
const people = [];
|
|
1015
1043
|
const nodes = [];
|
|
@@ -1092,8 +1120,13 @@ window.__DK_UI_VERSION="0.1.110";
|
|
|
1092
1120
|
return { match: partial[0], candidates: partial };
|
|
1093
1121
|
return { match: null, candidates: partial };
|
|
1094
1122
|
}
|
|
1095
|
-
function loadServicePeers() {
|
|
1096
|
-
|
|
1123
|
+
async function loadServicePeers() {
|
|
1124
|
+
try {
|
|
1125
|
+
const local = await fetch("/api/assistant/service-peers").then((r) => r.json());
|
|
1126
|
+
return mergeServicePeers(service_peers_default, local);
|
|
1127
|
+
} catch (e) {
|
|
1128
|
+
return service_peers_default;
|
|
1129
|
+
}
|
|
1097
1130
|
}
|
|
1098
1131
|
var ASSISTANT_PEER = {
|
|
1099
1132
|
id: ASSISTANT_ID,
|
|
@@ -1149,15 +1182,18 @@ window.__DK_UI_VERSION="0.1.110";
|
|
|
1149
1182
|
const tail = lang === "zh" ? `\u7B5B\u9009\u6761\u4EF6\uFF08\u53EF\u7EC4\u5408\uFF0C\u53D6\u5E76\u96C6\uFF09\uFF1A
|
|
1150
1183
|
${filters}
|
|
1151
1184
|
|
|
1152
|
-
|
|
1185
|
+
\`/rename\`\u3001\`/mute\`\u3001\`/find\` \u5BF9\u597D\u53CB\u548C\u670D\u52A1\u8282\u70B9\u90FD\u9002\u7528\uFF1B\u53EA\u6709\u5217\u8868\u548C\u5220\u9664\u5206\u6210\u4E24\u8FB9\u3002
|
|
1186
|
+
\u6700\u8FD1 ${RECENT_DAYS} \u5929\u5185\u6DFB\u52A0\u7684\u597D\u53CB\u4E0D\u4F1A\u88AB"\u4ECE\u672A/\u6C89\u9ED8"\u7C7B\u7B5B\u9009\u9009\u4E2D\uFF0C\u5E76\u4F1A\u5217\u51FA\u88AB\u8DF3\u8FC7\u7684\u540D\u5B57\u3002\u5220\u9664\u6C38\u8FDC\u9700\u8981\u5148\u786E\u8BA4\uFF0C\u53D6\u6D88\u52FE\u9009\u5373\u53EF\u4FDD\u7559\u3002` : `Filters (combine them; a friend matching any one is listed):
|
|
1153
1187
|
${filters}
|
|
1154
1188
|
|
|
1155
|
-
|
|
1189
|
+
\`/rename\`, \`/mute\` and \`/find\` work on friends and service peers alike \u2014 only listing and removing are split in two.
|
|
1190
|
+
Friends added in the last ${RECENT_DAYS} days are never matched by the "never/silent" filters, and the ones held back are named. Removal always asks first: untick anyone you want to keep.`;
|
|
1156
1191
|
return `${intro}
|
|
1157
1192
|
${cmds}
|
|
1158
1193
|
|
|
1159
1194
|
${tail}`;
|
|
1160
1195
|
}
|
|
1196
|
+
var SKIP_NAMES_MAX = 10;
|
|
1161
1197
|
var seq = 0;
|
|
1162
1198
|
var asMsg = (from, text, extra) => Object.assign({
|
|
1163
1199
|
id: `as-${Date.now()}-${seq++}`,
|
|
@@ -1165,8 +1201,9 @@ ${tail}`;
|
|
|
1165
1201
|
time: (/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }),
|
|
1166
1202
|
text
|
|
1167
1203
|
}, extra);
|
|
1168
|
-
function AssistantPanel({ m, T, lang, onRemove }) {
|
|
1204
|
+
function AssistantPanel({ m, T, lang, onRemove, onAdopt }) {
|
|
1169
1205
|
const rows = m.assistant.rows || [];
|
|
1206
|
+
const adopt = m.assistant.kind === "adopt";
|
|
1170
1207
|
const [sel, setSel] = React.useState(() => new Set(rows.map((r) => r.userid)));
|
|
1171
1208
|
const [busy, setBusy] = React.useState(false);
|
|
1172
1209
|
const [done, setDone] = React.useState(null);
|
|
@@ -1177,7 +1214,7 @@ ${tail}`;
|
|
|
1177
1214
|
n.has(id) ? n.delete(id) : n.add(id);
|
|
1178
1215
|
return n;
|
|
1179
1216
|
});
|
|
1180
|
-
const label = lang === "zh" ? `\u5220\u9664 ${sel.size} \u4F4D` : `Remove ${sel.size}`;
|
|
1217
|
+
const label = adopt ? lang === "zh" ? `\u52A0\u5165 ${sel.size} \u4E2A` : `Add ${sel.size} as ${sel.size === 1 ? "a node" : "nodes"}` : lang === "zh" ? `\u5220\u9664 ${sel.size} \u4F4D` : `Remove ${sel.size}`;
|
|
1181
1218
|
return /* @__PURE__ */ React.createElement("div", { style: { marginTop: 8, border: "1px solid var(--line)", borderRadius: 10, overflow: "hidden" } }, rows.map((r) => /* @__PURE__ */ React.createElement("label", { key: r.userid, style: {
|
|
1182
1219
|
display: "flex",
|
|
1183
1220
|
alignItems: "center",
|
|
@@ -1194,12 +1231,14 @@ ${tail}`;
|
|
|
1194
1231
|
disabled: !!done || busy,
|
|
1195
1232
|
onChange: () => toggle(r.userid)
|
|
1196
1233
|
}
|
|
1197
|
-
), /* @__PURE__ */ React.createElement("span", { style: { flex: 1 } }, nameOf(r)), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--faint)" } }, r.userid.slice(0, 8), "\u2026"))), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10, padding: "8px 10px" } }, done ? /* @__PURE__ */ React.createElement("span", { style: { fontSize: 12.5, color: "var(--faint)" } }, done) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Btn, { size: "sm", tone: "danger", disabled: busy || !sel.size, onClick: () => {
|
|
1234
|
+
), /* @__PURE__ */ React.createElement("span", { style: { flex: 1 } }, nameOf(r), r.why && /* @__PURE__ */ React.createElement("span", { style: { color: "var(--faint)", fontSize: 11.5 } }, ` \u2014 ${r.why}`)), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--faint)" } }, r.userid.slice(0, 8), "\u2026"))), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10, padding: "8px 10px" } }, done ? /* @__PURE__ */ React.createElement("span", { style: { fontSize: 12.5, color: "var(--faint)" } }, done) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Btn, { size: "sm", tone: adopt ? "solid" : "danger", disabled: busy || !sel.size, onClick: () => {
|
|
1198
1235
|
setBusy(true);
|
|
1199
|
-
|
|
1200
|
-
|
|
1236
|
+
const picked = rows.filter((r) => sel.has(r.userid));
|
|
1237
|
+
const run = adopt ? onAdopt(picked) : onRemove([...sel]);
|
|
1238
|
+
run.then((r) => {
|
|
1239
|
+
setDone(adopt ? lang === "zh" ? `\u5DF2\u52A0\u5165 ${r.added} \u4E2A\u670D\u52A1\u8282\u70B9` : `Added ${r.added} as service peers` : lang === "zh" ? `\u5DF2\u5220\u9664 ${r.removed} \u4F4D${r.failed ? `\uFF0C${r.failed} \u4F4D\u5931\u8D25` : ""}` : `Removed ${r.removed}${r.failed ? `, ${r.failed} failed` : ""}`);
|
|
1201
1240
|
}).finally(() => setBusy(false));
|
|
1202
|
-
} }, busy ? T.working || "Working\u2026" : label), /* @__PURE__ */ React.createElement("span", { style: { fontSize: 12, color: "var(--faint)" } }, lang === "zh" ? "\u53D6\u6D88\u52FE\u9009\u53EF\u4FDD\u7559" : "Untick anyone you want to keep"))));
|
|
1241
|
+
} }, busy ? T.working || "Working\u2026" : label), /* @__PURE__ */ React.createElement("span", { style: { fontSize: 12, color: "var(--faint)" } }, adopt ? lang === "zh" ? "\u53D6\u6D88\u52FE\u9009\u5373\u4FDD\u6301\u4E3A\u597D\u53CB" : "Untick any you want to keep as a friend" : lang === "zh" ? "\u53D6\u6D88\u52FE\u9009\u53EF\u4FDD\u7559" : "Untick anyone you want to keep"))));
|
|
1203
1242
|
}
|
|
1204
1243
|
function useAssistant({ lang, T, muted, onToggleMute, sound, flash, setAlert, onReloadFriends }) {
|
|
1205
1244
|
const [thread, setThread] = React.useState(() => [{ day: (/* @__PURE__ */ new Date()).toLocaleDateString() }, asMsg("them", helpText(lang))]);
|
|
@@ -1209,32 +1248,41 @@ ${tail}`;
|
|
|
1209
1248
|
}, [lang]);
|
|
1210
1249
|
const fetchFriends = async () => {
|
|
1211
1250
|
const r = await fetch("/api/assistant/friends").then((x) => x.json());
|
|
1212
|
-
const
|
|
1251
|
+
const svcMap = servicePeerMap(await loadServicePeers());
|
|
1252
|
+
const { people, nodes } = partitionPeers(r.friends || [], svcMap);
|
|
1213
1253
|
return {
|
|
1214
1254
|
friends: people,
|
|
1215
1255
|
nodes,
|
|
1216
1256
|
all: r.friends || [],
|
|
1257
|
+
svcMap,
|
|
1217
1258
|
historySince: r.historySince || null,
|
|
1218
1259
|
now: r.now || Date.now(),
|
|
1219
1260
|
ver: r.beagleVer || ""
|
|
1220
1261
|
};
|
|
1221
1262
|
};
|
|
1263
|
+
const skipLine = (skipped) => {
|
|
1264
|
+
if (!skipped.length)
|
|
1265
|
+
return "";
|
|
1266
|
+
const shown = skipped.slice(0, SKIP_NAMES_MAX).map(nameOf);
|
|
1267
|
+
const more = skipped.length - shown.length;
|
|
1268
|
+
const names = shown.join(", ") + (more > 0 ? lang === "zh" ? ` \u7B49 ${more} \u4F4D` : `, and ${more} more` : "");
|
|
1269
|
+
return lang === "zh" ? `
|
|
1270
|
+
|
|
1271
|
+
*\u8DF3\u8FC7\u4E86 ${skipped.length} \u4F4D\u6700\u8FD1 ${RECENT_DAYS} \u5929\u5185\u6DFB\u52A0\u7684\u597D\u53CB\uFF1A${names}\u3002*` : `
|
|
1272
|
+
|
|
1273
|
+
*Skipped ${skipped.length} added in the last ${RECENT_DAYS} days: ${names}.*`;
|
|
1274
|
+
};
|
|
1222
1275
|
const listReply = (rows, skipped, data, title) => {
|
|
1223
1276
|
if (!rows.length) {
|
|
1224
1277
|
return `${title}
|
|
1225
1278
|
|
|
1226
|
-
${lang === "zh" ? "\u6CA1\u6709\u7B26\u5408\u7684\u597D\u53CB\u3002" : "No friends match."}
|
|
1279
|
+
${lang === "zh" ? "\u6CA1\u6709\u7B26\u5408\u7684\u597D\u53CB\u3002" : "No friends match."}${skipLine(skipped)}
|
|
1227
1280
|
|
|
1228
1281
|
${sinceLine(data.historySince, lang)}`;
|
|
1229
1282
|
}
|
|
1230
|
-
const skip = skipped.length ? lang === "zh" ? `
|
|
1231
|
-
|
|
1232
|
-
*\u8DF3\u8FC7\u4E86 ${skipped.length} \u4F4D\u6700\u8FD1 ${RECENT_DAYS} \u5929\u5185\u6DFB\u52A0\u7684\u597D\u53CB\u3002*` : `
|
|
1233
|
-
|
|
1234
|
-
*${skipped.length} friend${skipped.length > 1 ? "s" : ""} added in the last ${RECENT_DAYS} days ${skipped.length > 1 ? "were" : "was"} skipped.*` : "";
|
|
1235
1283
|
return `${title}
|
|
1236
1284
|
|
|
1237
|
-
${friendTable(rows, data.now, lang)}${
|
|
1285
|
+
${friendTable(rows, data.now, lang)}${skipLine(skipped)}
|
|
1238
1286
|
|
|
1239
1287
|
${sinceLine(data.historySince, lang)}`;
|
|
1240
1288
|
};
|
|
@@ -1273,6 +1321,33 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
|
|
|
1273
1321
|
- Waiting to be accepted: ${pending}
|
|
1274
1322
|
- History on this device since: ${fmtDate(d.historySince, lang)}
|
|
1275
1323
|
- Version: ${d.ver}`));
|
|
1324
|
+
}
|
|
1325
|
+
case "nodes-refresh": {
|
|
1326
|
+
const d = await fetchFriends();
|
|
1327
|
+
const r = await fetch("/api/assistant/service-candidates").then((x) => x.json());
|
|
1328
|
+
const fresh = newCandidates(r.candidates || [], d.svcMap);
|
|
1329
|
+
const src = r.sources || {};
|
|
1330
|
+
const where = lang === "zh" ? `\u67E5\u8BE2\u6765\u6E90\uFF1A\u5185\u7F6E\u51FA\u53E3\u8868\uFF08${src.exits || 0} \u6761\uFF09${src.dora == null ? "\uFF0C\u672C\u673A\u4E0D\u662F dora" : `\uFF0C\u672C\u673A dora \u540D\u518C\uFF08${src.dora} \u6761\uFF09`}` : `Looked at: the shipped exit table (${src.exits || 0})${src.dora == null ? ", and this machine is not a dora" : `, and this machine's dora roster (${src.dora})`}`;
|
|
1331
|
+
if (!fresh.length) {
|
|
1332
|
+
return append(asMsg("them", lang === "zh" ? `**\u6CA1\u6709\u65B0\u7684\u670D\u52A1\u8282\u70B9**
|
|
1333
|
+
|
|
1334
|
+
\u73B0\u6709 ${d.nodes.length} \u4E2A\u5DF2\u8BC6\u522B\u3002${where}\u3002
|
|
1335
|
+
|
|
1336
|
+
*\u53EA\u91C7\u7528\u8BC1\u636E\uFF0C\u4E0D\u9760\u540D\u5B57\u731C\u6D4B\u3002*` : `**Nothing new**
|
|
1337
|
+
|
|
1338
|
+
${d.nodes.length} service peers are already classified. ${where}.
|
|
1339
|
+
|
|
1340
|
+
*Evidence only \u2014 names are never guessed from.*`));
|
|
1341
|
+
}
|
|
1342
|
+
const title = lang === "zh" ? `**\u53D1\u73B0 ${fresh.length} \u4E2A\u53EF\u4EE5\u6807\u4E3A\u670D\u52A1\u8282\u70B9\u7684\u597D\u53CB** \u2014 \u52FE\u9009\u540E\u624D\u4F1A\u751F\u6548
|
|
1343
|
+
|
|
1344
|
+
${where}\u3002` : `**${fresh.length === 1 ? "1 friend looks" : `${fresh.length} friends look`} like service peers** \u2014 nothing changes until you tick and confirm
|
|
1345
|
+
|
|
1346
|
+
${where}.`;
|
|
1347
|
+
const lines = fresh.map((c) => `- **${c.name}** \`${c.kind}${c.role ? " \xB7 " + c.role : ""}\` \u2014 ${c.why}`).join("\n");
|
|
1348
|
+
return append(asMsg("them", `${title}
|
|
1349
|
+
|
|
1350
|
+
${lines}`, { assistant: { kind: "adopt", rows: fresh } }));
|
|
1276
1351
|
}
|
|
1277
1352
|
case "nodes-list":
|
|
1278
1353
|
case "friends-list": {
|
|
@@ -1362,6 +1437,24 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
|
|
|
1362
1437
|
return append(asMsg("them", lang === "zh" ? `\u51FA\u9519\u4E86\uFF1A${e && e.message || e}` : `That failed: ${e && e.message || e}`));
|
|
1363
1438
|
}
|
|
1364
1439
|
}, [append, lang, muted, onToggleMute, setAlert, onReloadFriends]);
|
|
1440
|
+
const adoptServicePeers = React.useCallback(async (rows) => {
|
|
1441
|
+
const add = rows.map((r) => ({ userid: r.userid, name: r.name, kind: r.kind, role: r.role }));
|
|
1442
|
+
let added = 0;
|
|
1443
|
+
try {
|
|
1444
|
+
const r = await fetch("/api/assistant/service-peers", {
|
|
1445
|
+
method: "POST",
|
|
1446
|
+
headers: { "content-type": "application/json" },
|
|
1447
|
+
body: JSON.stringify({ add })
|
|
1448
|
+
}).then((x) => x.json());
|
|
1449
|
+
added = r && r.ok ? add.length : 0;
|
|
1450
|
+
} catch (e) {
|
|
1451
|
+
added = 0;
|
|
1452
|
+
}
|
|
1453
|
+
if (onReloadFriends)
|
|
1454
|
+
onReloadFriends();
|
|
1455
|
+
append(asMsg("them", added ? lang === "zh" ? `\u5DF2\u628A ${added} \u4F4D\u6807\u4E3A\u670D\u52A1\u8282\u70B9\uFF0C\u5B83\u4EEC\u4E0D\u518D\u51FA\u73B0\u5728 \`/friends\` \u4E2D\u3002` : `${added} now count as service peers \u2014 they no longer appear in \`/friends\`.` : lang === "zh" ? "\u5199\u5165\u5931\u8D25\uFF0C\u6CA1\u6709\u4EFB\u4F55\u6539\u52A8\u3002" : "That write failed; nothing changed."));
|
|
1456
|
+
return { added };
|
|
1457
|
+
}, [append, lang, onReloadFriends]);
|
|
1365
1458
|
const removeFriends = React.useCallback(async (ids) => {
|
|
1366
1459
|
let removed = 0;
|
|
1367
1460
|
const failed = [];
|
|
@@ -1381,7 +1474,7 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
|
|
|
1381
1474
|
append(asMsg("them", failed.length ? lang === "zh" ? `\u5DF2\u5220\u9664 ${removed} \u4F4D\uFF0C${failed.length} \u4F4D\u5931\u8D25\u3002` : `Removed ${removed}, ${failed.length} failed.` : lang === "zh" ? `\u5DF2\u5220\u9664 ${removed} \u4F4D\u597D\u53CB\u3002` : `Removed ${removed} friend${removed === 1 ? "" : "s"}.`));
|
|
1382
1475
|
return { removed, failed: failed.length };
|
|
1383
1476
|
}, [append, lang, onReloadFriends]);
|
|
1384
|
-
return { thread, handle, removeFriends };
|
|
1477
|
+
return { thread, handle, removeFriends, adoptServicePeers };
|
|
1385
1478
|
}
|
|
1386
1479
|
var DK_CMD_HIST_KEY = "beagle.assistant.history";
|
|
1387
1480
|
var DK_CMD_HIST_MAX = 50;
|
|
@@ -2188,7 +2281,7 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
|
|
|
2188
2281
|
return null;
|
|
2189
2282
|
}), err && /* @__PURE__ */ React.createElement("div", { style: { color: "var(--danger, #ff6b6b)", fontSize: 11.5, fontFamily: "var(--mono)" } }, err));
|
|
2190
2283
|
}
|
|
2191
|
-
function Msg({ m, peer, T, lang, onTheater, onDelete, onCancel, onRetry, onRetryMsg, onReveal, onOpenFile, onCall, onFormSubmit, onNameCard, onAssistantRemove, answered, selMode, selected, onToggleSel, busy, isGroup }) {
|
|
2284
|
+
function Msg({ m, peer, T, lang, onTheater, onDelete, onCancel, onRetry, onRetryMsg, onReveal, onOpenFile, onCall, onFormSubmit, onNameCard, onAssistantRemove, onAssistantAdopt, answered, selMode, selected, onToggleSel, busy, isGroup }) {
|
|
2192
2285
|
const mine = m.from === "me";
|
|
2193
2286
|
const nativeMedia = !m.file ? dkNativeMediaFromText(m.text) : null;
|
|
2194
2287
|
const grp = isGroup && m.dir !== "out" && !m.file && !nativeMedia ? dkGroupParse(m.text) : null;
|
|
@@ -2517,7 +2610,7 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
|
|
|
2517
2610
|
letterSpacing: -0.1,
|
|
2518
2611
|
wordBreak: "break-word"
|
|
2519
2612
|
} }, /* @__PURE__ */ React.createElement(MarkdownText, { text: read.text, onNameCard })));
|
|
2520
|
-
})(), read.form && /* @__PURE__ */ React.createElement("div", { style: { marginTop: 6 } }, /* @__PURE__ */ React.createElement(DkChatForm, { form: read.form, submitted, peer, onSubmit: onFormSubmit })), m.assistant && onAssistantRemove && /* @__PURE__ */ React.createElement(AssistantPanel, { m, T, lang, onRemove: onAssistantRemove }), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 4, margin: "3px 3px 0" } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10, color: "var(--faint)" } }, m.time), (() => {
|
|
2613
|
+
})(), read.form && /* @__PURE__ */ React.createElement("div", { style: { marginTop: 6 } }, /* @__PURE__ */ React.createElement(DkChatForm, { form: read.form, submitted, peer, onSubmit: onFormSubmit })), m.assistant && onAssistantRemove && /* @__PURE__ */ React.createElement(AssistantPanel, { m, T, lang, onRemove: onAssistantRemove, onAdopt: onAssistantAdopt }), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 4, margin: "3px 3px 0" } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10, color: "var(--faint)" } }, m.time), (() => {
|
|
2521
2614
|
const sentDone = mine && m.status && m.status !== "queued" && m.status !== "sending" && m.status !== "failed";
|
|
2522
2615
|
const via = !mine ? m.via : sentDone ? m.via === "offline" ? "offline" : "online" : null;
|
|
2523
2616
|
if (!via)
|
|
@@ -2562,7 +2655,7 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
|
|
|
2562
2655
|
) : mine && m.via === "offline" && m.status !== "queued" && m.status !== "sending" ? /* @__PURE__ */ React.createElement(Icon, { name: "check", size: 12, stroke: 2.2, color: "#f59e0b", title: T.viaOutOffline || "posted to the offline relay \u2014 they get it when they come online" }) : mine && m.status === "sent" && m.confirmed === false ? /* @__PURE__ */ React.createElement(Icon, { name: "check", size: 12, stroke: 2.2, color: "var(--faint)", title: T.sentUnconfirmed || "sent \u2014 not confirmed by the peer" }) : mine && m.status && /* @__PURE__ */ React.createElement(Icon, { name: "checkCheck", size: 12, stroke: 2.2, color: m.status === "read" ? "var(--accent)" : "var(--faint)" })))
|
|
2563
2656
|
);
|
|
2564
2657
|
}
|
|
2565
|
-
function Conversation({ T, peer, lang, peers, onOpenChat, thread: threadProp, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, muted, onToggleMute, phone, onBack }) {
|
|
2658
|
+
function Conversation({ T, peer, lang, peers, onOpenChat, thread: threadProp, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, onAssistantAdopt, muted, onToggleMute, phone, onBack }) {
|
|
2566
2659
|
const scrollRef = React.useRef(null);
|
|
2567
2660
|
const prependRef = React.useRef(null);
|
|
2568
2661
|
const [earlierBusy, setEarlierBusy] = React.useState(false);
|
|
@@ -2995,6 +3088,7 @@ ${peer.address}`
|
|
|
2995
3088
|
isGroup,
|
|
2996
3089
|
onTheater: setTheater,
|
|
2997
3090
|
onAssistantRemove,
|
|
3091
|
+
onAssistantAdopt,
|
|
2998
3092
|
onDelete: (id) => doDelete([id]),
|
|
2999
3093
|
onCancel: doCancel,
|
|
3000
3094
|
onRetry: doRetry,
|
|
@@ -3154,12 +3248,12 @@ ${peer.address}`
|
|
|
3154
3248
|
function ChatEmpty({ T }) {
|
|
3155
3249
|
return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 12, color: "var(--faint)", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "message", size: 40, stroke: 1.4, color: "var(--line)" }), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13 } }, T.pickPeer));
|
|
3156
3250
|
}
|
|
3157
|
-
function ChatTab({ T, lang, peers, requests, activeId, thread, onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, prefillAddr, onPrefillConsumed, muted, onToggleMute, phone, onBack }) {
|
|
3251
|
+
function ChatTab({ T, lang, peers, requests, activeId, thread, onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, onAssistantAdopt, prefillAddr, onPrefillConsumed, muted, onToggleMute, phone, onBack }) {
|
|
3158
3252
|
const peer = peers.find((p) => p.id === activeId);
|
|
3159
3253
|
if (phone) {
|
|
3160
|
-
return peer ? /* @__PURE__ */ React.createElement(Conversation, { T, peer, lang, peers, onOpenChat: onSelect, thread, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, muted: !!muted && muted.includes(peer.id), onToggleMute, phone: true, onBack }) : /* @__PURE__ */ React.createElement(PeerSidebar, { T, peers, requests, activeId, onSelect, onAct, onAdd, prefillAddr, onPrefillConsumed, muted, wide: true });
|
|
3254
|
+
return peer ? /* @__PURE__ */ React.createElement(Conversation, { T, peer, lang, peers, onOpenChat: onSelect, thread, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, onAssistantAdopt, muted: !!muted && muted.includes(peer.id), onToggleMute, phone: true, onBack }) : /* @__PURE__ */ React.createElement(PeerSidebar, { T, peers, requests, activeId, onSelect, onAct, onAdd, prefillAddr, onPrefillConsumed, muted, wide: true });
|
|
3161
3255
|
}
|
|
3162
|
-
return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, display: "flex", minWidth: 0, minHeight: 0 } }, /* @__PURE__ */ React.createElement(PeerSidebar, { T, peers, requests, activeId, onSelect, onAct, onAdd, prefillAddr, onPrefillConsumed, muted }), peer ? /* @__PURE__ */ React.createElement(Conversation, { T, peer, lang, peers, onOpenChat: onSelect, thread, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, muted: !!muted && muted.includes(peer.id), onToggleMute }) : /* @__PURE__ */ React.createElement(ChatEmpty, { T }));
|
|
3256
|
+
return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, display: "flex", minWidth: 0, minHeight: 0 } }, /* @__PURE__ */ React.createElement(PeerSidebar, { T, peers, requests, activeId, onSelect, onAct, onAdd, prefillAddr, onPrefillConsumed, muted }), peer ? /* @__PURE__ */ React.createElement(Conversation, { T, peer, lang, peers, onOpenChat: onSelect, thread, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, onAssistantAdopt, muted: !!muted && muted.includes(peer.id), onToggleMute }) : /* @__PURE__ */ React.createElement(ChatEmpty, { T }));
|
|
3163
3257
|
}
|
|
3164
3258
|
Object.assign(window, { ChatTab });
|
|
3165
3259
|
function CopyBtn({ value, copiedText = "Copied", copyFailedText = "Copy failed", copyTitle = "Copy" }) {
|
|
@@ -6756,7 +6850,7 @@ ${peer.address}`
|
|
|
6756
6850
|
fontWeight: 600,
|
|
6757
6851
|
background: tab === n.id ? "var(--accent)" : "transparent",
|
|
6758
6852
|
color: tab === n.id ? "#fff" : "var(--dim)"
|
|
6759
|
-
} }, /* @__PURE__ */ React.createElement(Icon, { name: n.icon, size: 14, stroke: 2, color: tab === n.id ? "#fff" : "var(--dim)" }), n.label))), tab === "chat" && /* @__PURE__ */ React.createElement(ChatTab, { T, lang: t.lang, phone, onBack: closeConversation, peers, requests, activeId, thread: activeId === ASSISTANT_ID ? assistant.thread : data.threads[activeId], onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread: () => activeId && data.loadThread(activeId), hasEarlier: !!(activeId && activeId !== ASSISTANT_ID && data.earlier[activeId]), onAssistantRemove: activeId === ASSISTANT_ID ? assistant.removeFriends : null, onLoadEarlier: () => activeId && data.loadEarlier(activeId), prefillAddr: pendingAddr, onPrefillConsumed: () => setPendingAddr(""), muted: mutedIds, onToggleMute }), tab === "here" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "bridge", peers, meId: me.userId, onAdd, onOpenChat }), tab === "recommended" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "recommended", peers, meId: me.userId, onAdd, onOpenChat }), tab === "registered" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "registered", peers, meId: me.userId, onAdd, onOpenChat }), tab === "network" && /* @__PURE__ */ React.createElement(NetworkTab, { T, me, peers, exits, activeExit, reqCount: requests.length, onSetExit, onOpenChat, backend, onArmLan: armLan, onCancelLan: cancelLan }), tab === "apps" && /* @__PURE__ */ React.createElement(AppsTab, { T, t }), tab === "profile" && /* @__PURE__ */ React.createElement(ProfileTab, { T, me, onEdit, t, setTweak, onSetAvatar: (p) => dkApi.setProfileFull(p).then((r) => {
|
|
6853
|
+
} }, /* @__PURE__ */ React.createElement(Icon, { name: n.icon, size: 14, stroke: 2, color: tab === n.id ? "#fff" : "var(--dim)" }), n.label))), tab === "chat" && /* @__PURE__ */ React.createElement(ChatTab, { T, lang: t.lang, phone, onBack: closeConversation, peers, requests, activeId, thread: activeId === ASSISTANT_ID ? assistant.thread : data.threads[activeId], onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread: () => activeId && data.loadThread(activeId), hasEarlier: !!(activeId && activeId !== ASSISTANT_ID && data.earlier[activeId]), onAssistantRemove: activeId === ASSISTANT_ID ? assistant.removeFriends : null, onAssistantAdopt: activeId === ASSISTANT_ID ? assistant.adoptServicePeers : null, onLoadEarlier: () => activeId && data.loadEarlier(activeId), prefillAddr: pendingAddr, onPrefillConsumed: () => setPendingAddr(""), muted: mutedIds, onToggleMute }), tab === "here" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "bridge", peers, meId: me.userId, onAdd, onOpenChat }), tab === "recommended" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "recommended", peers, meId: me.userId, onAdd, onOpenChat }), tab === "registered" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "registered", peers, meId: me.userId, onAdd, onOpenChat }), tab === "network" && /* @__PURE__ */ React.createElement(NetworkTab, { T, me, peers, exits, activeExit, reqCount: requests.length, onSetExit, onOpenChat, backend, onArmLan: armLan, onCancelLan: cancelLan }), tab === "apps" && /* @__PURE__ */ React.createElement(AppsTab, { T, t }), tab === "profile" && /* @__PURE__ */ React.createElement(ProfileTab, { T, me, onEdit, t, setTweak, onSetAvatar: (p) => dkApi.setProfileFull(p).then((r) => {
|
|
6760
6854
|
data.refresh();
|
|
6761
6855
|
return r;
|
|
6762
6856
|
}) })), phone && !inConversation && /* @__PURE__ */ React.createElement("div", { style: { flexShrink: 0, display: "flex", borderTop: "1px solid var(--line)", background: "var(--rail)", paddingBottom: "env(safe-area-inset-bottom, 0px)" } }, phoneNav.map((n) => /* @__PURE__ */ React.createElement(TabBarBtn, { key: n.id, icon: n.icon, label: n.label, active: n.active, badge: n.badge, onClick: n.go }))), data.locked && /* @__PURE__ */ React.createElement(
|
package/dist/server.d.ts
CHANGED
|
@@ -28,6 +28,10 @@ export interface BeagleServerOptions {
|
|
|
28
28
|
* (never written) by GET /api/assistant/friends to count a friend's
|
|
29
29
|
* history without hauling it over IPC. Undefined = no counts. */
|
|
30
30
|
messagesPath?: string;
|
|
31
|
+
/** <configDir>/service-peers-local.json — peers THIS machine has adopted as
|
|
32
|
+
* service peers on top of the list shipped in the app. Written only by the
|
|
33
|
+
* Assistant's `/nodes refresh`, and only after the user ticks the rows. */
|
|
34
|
+
servicePeersPath?: string;
|
|
31
35
|
/** The swappable backend, so the UI can show which one is live and drive the
|
|
32
36
|
* LAN handoff. Optional: a caller that pins one backend just omits it and
|
|
33
37
|
* the network panel falls back to "no virtual LAN here". */
|
package/dist/server.js
CHANGED
|
@@ -121,6 +121,21 @@ function friendStoreStamps(keyFile) {
|
|
|
121
121
|
return friendStampsCache?.map ?? empty;
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
|
+
/** The machine-local service-peer overlay. Absent file = nothing adopted yet,
|
|
125
|
+
* which is the normal state, not an error. */
|
|
126
|
+
function readLocalServicePeers(path) {
|
|
127
|
+
const empty = { version: 1, source: "local", peers: [] };
|
|
128
|
+
if (!path || !existsSync(path))
|
|
129
|
+
return empty;
|
|
130
|
+
try {
|
|
131
|
+
const raw = JSON.parse(readFileSync(path, "utf-8"));
|
|
132
|
+
const peers = Array.isArray(raw?.peers) ? raw.peers.filter((p) => p && typeof p.userid === "string") : [];
|
|
133
|
+
return { version: 1, source: "local", updated: raw?.updated, peers };
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return empty;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
124
139
|
/** The record beagle writes for its own name: what the profile page shows
|
|
125
140
|
* (displayName, description) on top of whatever the record already held —
|
|
126
141
|
* wallet binds, socials, avatar, referee — so a profile edit never strips a
|
|
@@ -2238,6 +2253,95 @@ export function startBeagleServer(opts) {
|
|
|
2238
2253
|
sendJson(res, 200, { me, peers, requests, exits, activeExit });
|
|
2239
2254
|
return;
|
|
2240
2255
|
}
|
|
2256
|
+
// The locally adopted service peers: the app ships a list, and this is
|
|
2257
|
+
// what this machine added to it. Kept server-side rather than in a tab's
|
|
2258
|
+
// localStorage so every browser on this machine agrees about what is a
|
|
2259
|
+
// node — the classification decides what `/friends remove` can touch.
|
|
2260
|
+
if (req.method === "GET" && url === "/api/assistant/service-peers") {
|
|
2261
|
+
sendJson(res, 200, readLocalServicePeers(opts.servicePeersPath));
|
|
2262
|
+
return;
|
|
2263
|
+
}
|
|
2264
|
+
if (req.method === "POST" && url === "/api/assistant/service-peers") {
|
|
2265
|
+
const body = (await readBody(req));
|
|
2266
|
+
if (!opts.servicePeersPath)
|
|
2267
|
+
return sendJson(res, 400, { ok: false, error: "no config dir" });
|
|
2268
|
+
const current = readLocalServicePeers(opts.servicePeersPath);
|
|
2269
|
+
const byId = new Map(current.peers.map((p) => [p.userid, p]));
|
|
2270
|
+
for (const raw of Array.isArray(body.add) ? body.add : []) {
|
|
2271
|
+
const p = raw;
|
|
2272
|
+
const userid = typeof p?.userid === "string" ? p.userid : "";
|
|
2273
|
+
if (!userid)
|
|
2274
|
+
continue;
|
|
2275
|
+
byId.set(userid, {
|
|
2276
|
+
userid,
|
|
2277
|
+
name: String(p.name ?? userid).slice(0, 120),
|
|
2278
|
+
kind: p.kind === "agent" ? "agent" : "node",
|
|
2279
|
+
role: String(p.role ?? "").slice(0, 120),
|
|
2280
|
+
});
|
|
2281
|
+
}
|
|
2282
|
+
for (const raw of Array.isArray(body.remove) ? body.remove : []) {
|
|
2283
|
+
if (typeof raw === "string")
|
|
2284
|
+
byId.delete(raw);
|
|
2285
|
+
}
|
|
2286
|
+
const next = { version: 1, source: "local", updated: new Date().toISOString().slice(0, 10), peers: [...byId.values()] };
|
|
2287
|
+
try {
|
|
2288
|
+
writeFileSync(opts.servicePeersPath, JSON.stringify(next, null, 2) + "\n");
|
|
2289
|
+
}
|
|
2290
|
+
catch (e) {
|
|
2291
|
+
return sendJson(res, 500, { ok: false, error: e.message });
|
|
2292
|
+
}
|
|
2293
|
+
sendJson(res, 200, { ok: true, ...next });
|
|
2294
|
+
return;
|
|
2295
|
+
}
|
|
2296
|
+
// `/nodes refresh`: which friends does this device have EVIDENCE for?
|
|
2297
|
+
// Evidence only — the shipped exit table and, on a dora, its roster.
|
|
2298
|
+
// Nothing here reads a name or guesses from silence, and nothing is
|
|
2299
|
+
// adopted without the user ticking it.
|
|
2300
|
+
if (req.method === "GET" && url === "/api/assistant/service-candidates") {
|
|
2301
|
+
const flist = await opts.call({ op: "friends-list" });
|
|
2302
|
+
const fl = flist.ok ? (flist.data?.friends ?? []) : [];
|
|
2303
|
+
const evidence = new Map();
|
|
2304
|
+
for (const e of DEFAULT_EXITS) {
|
|
2305
|
+
// Role, not region: the exit table's regions carry place names this
|
|
2306
|
+
// app does not put in front of users (see CLAUDE.md), and "which
|
|
2307
|
+
// exit" is not what this answer is for.
|
|
2308
|
+
if (e.userid)
|
|
2309
|
+
evidence.set(e.userid, { kind: "node", role: "exit", why: "in the shipped exit table" });
|
|
2310
|
+
}
|
|
2311
|
+
let doraCount = 0;
|
|
2312
|
+
if (opts.doraRosterPath && existsSync(opts.doraRosterPath)) {
|
|
2313
|
+
try {
|
|
2314
|
+
const r = yaml.load(readFileSync(opts.doraRosterPath, "utf-8"));
|
|
2315
|
+
for (const rec of r?.records ?? []) {
|
|
2316
|
+
const uid = rec.userid || rec.pubkey;
|
|
2317
|
+
if (!uid)
|
|
2318
|
+
continue;
|
|
2319
|
+
doraCount++;
|
|
2320
|
+
evidence.set(uid, { kind: "node", role: rec.ip ? `dora member (${rec.ip})` : "dora member", why: "allocated in this machine's dora roster" });
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
catch { /* a broken roster is not an error here */ }
|
|
2324
|
+
}
|
|
2325
|
+
const candidates = fl
|
|
2326
|
+
.map((f) => ({ f, ev: evidence.get(f.userid ?? "") }))
|
|
2327
|
+
.filter((x) => x.ev)
|
|
2328
|
+
.map(({ f, ev }) => ({
|
|
2329
|
+
userid: f.userid,
|
|
2330
|
+
name: f.alias || (typeof f.name === "string" ? f.name : "") || f.userid,
|
|
2331
|
+
kind: ev.kind,
|
|
2332
|
+
role: ev.role,
|
|
2333
|
+
why: ev.why,
|
|
2334
|
+
}));
|
|
2335
|
+
sendJson(res, 200, {
|
|
2336
|
+
ok: true,
|
|
2337
|
+
candidates,
|
|
2338
|
+
sources: {
|
|
2339
|
+
exits: DEFAULT_EXITS.length,
|
|
2340
|
+
dora: opts.doraRosterPath ? doraCount : null,
|
|
2341
|
+
},
|
|
2342
|
+
});
|
|
2343
|
+
return;
|
|
2344
|
+
}
|
|
2241
2345
|
// Everything the Assistant's friend filters need, in one answer: the
|
|
2242
2346
|
// friend list plus how much was said to each friend and when. The
|
|
2243
2347
|
// counts come from the store file in our own config dir — see
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/beagle",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.112",
|
|
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",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"yargs": "^17.7.2"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@decentnetwork/beagle-ui": "^0.2.
|
|
38
|
+
"@decentnetwork/beagle-ui": "^0.2.26",
|
|
39
39
|
"@types/js-yaml": "^4.0.9",
|
|
40
40
|
"@types/node": "^20.11.0",
|
|
41
41
|
"@types/yargs": "^17.0.32",
|