@weaveprotocol/cli 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/weave.mjs +75 -6
  2. package/package.json +1 -1
package/dist/weave.mjs CHANGED
@@ -7527,14 +7527,18 @@ async function proveRead(access, label, provider) {
7527
7527
  }
7528
7528
  async function checkRead(access, peerDid, label, proof, provider) {
7529
7529
  const current = access.current();
7530
+ const admit = (readKey2) => {
7531
+ access.admitted?.(peerDid, readKey2);
7532
+ return true;
7533
+ };
7530
7534
  const claimed = typeof proof.readKey === "string" ? proof.readKey : current;
7531
7535
  if (claimed.startsWith("did:key:") && await verifyBy(provider, claimed, proof.read, label)) {
7532
- if (claimed === current) return true;
7533
- if (access.admits && await access.admits(peerDid, claimed, proof.member)) return true;
7536
+ if (claimed === current) return admit(current);
7537
+ if (access.admits && await access.admits(peerDid, claimed, proof.member)) return admit(claimed);
7534
7538
  }
7535
7539
  const earlier = Array.isArray(proof.earlier) ? proof.earlier.slice(0, MAX_EARLIER_READ_KEYS) : [];
7536
7540
  const ours = earlier.find((item) => item?.readKey === current);
7537
- return ours !== void 0 && current.startsWith("did:key:") && verifyBy(provider, current, ours.read, label);
7541
+ return ours !== void 0 && current.startsWith("did:key:") && await verifyBy(provider, current, ours.read, label) && admit(current);
7538
7542
  }
7539
7543
  function peerNonce() {
7540
7544
  return base64UrlEncode(globalThis.crypto.getRandomValues(new Uint8Array(16)));
@@ -8016,6 +8020,12 @@ function createMesh(config) {
8016
8020
  broadcast: (message) => {
8017
8021
  for (const peer of room.peers.keys()) send2(peer, message);
8018
8022
  },
8023
+ drop(peer) {
8024
+ if (rooms.get(name) !== room || !room.peers.has(peer)) return;
8025
+ sendFrame(peer, { room: name, type: LEAVE_MESSAGE });
8026
+ drop(room, peer);
8027
+ closeIfIdle(peer);
8028
+ },
8019
8029
  getPeers: () => [...room.peers.values()],
8020
8030
  on: room.events.on,
8021
8031
  off: room.events.off,
@@ -8066,6 +8076,9 @@ function createNetworkManager(config) {
8066
8076
  broadcast: (message) => {
8067
8077
  for (const peerId of peers.keys()) send2(peerId, message);
8068
8078
  },
8079
+ drop: (peerId) => {
8080
+ if (peers.has(peerId)) transport.close(peerId);
8081
+ },
8069
8082
  getPeers: () => [...peers.values()],
8070
8083
  on,
8071
8084
  off,
@@ -9011,6 +9024,8 @@ var writeCapability = (spaceId) => ({
9011
9024
  });
9012
9025
  var noteCid = (encoded) => cidFromBytes(utf8Encode(encoded));
9013
9026
  var NO_ACCOUNT = Object.freeze({ account: null, agent: false });
9027
+ var STALE_ADMISSION_MS = 6e4;
9028
+ var WHO_WAIT_MS = 1e4;
9014
9029
  var WHO_MESSAGE = "who";
9015
9030
  var LIVE_MESSAGE = "live";
9016
9031
  var MAX_LIVE_BYTES = 64 * 1024;
@@ -9564,6 +9579,8 @@ async function openSpaceRuntime(deps) {
9564
9579
  for (const mirror of mirrors) mirror.changed();
9565
9580
  void refreshHolds().catch(() => {
9566
9581
  });
9582
+ void letGoOfOutsiders().catch(() => {
9583
+ });
9567
9584
  };
9568
9585
  function currentKey2(state) {
9569
9586
  const epoch = state.keys.at(-1);
@@ -9721,6 +9738,7 @@ async function openSpaceRuntime(deps) {
9721
9738
  keysRunning = null;
9722
9739
  });
9723
9740
  }
9741
+ const admittedOn = /* @__PURE__ */ new Map();
9724
9742
  const readAccess = space2.visibility !== "private" ? null : {
9725
9743
  async key() {
9726
9744
  const held = newestKey((await access()).history.current);
@@ -9744,6 +9762,9 @@ async function openSpaceRuntime(deps) {
9744
9762
  if (!held) return false;
9745
9763
  const { account } = await accountOf(peerDid, await openWith(held, membership, membershipContext(space2.id)));
9746
9764
  return account !== null && readers(history.current).has(account);
9765
+ },
9766
+ admitted(peerDid, readKey2) {
9767
+ admittedOn.set(peerDid, { readKey: readKey2, at: Date.now() });
9747
9768
  }
9748
9769
  };
9749
9770
  const mirrors = [];
@@ -9853,6 +9874,24 @@ async function openSpaceRuntime(deps) {
9853
9874
  if (!routes.has(peer)) return;
9854
9875
  emit({ type: "message", space: space2.id, from: account, peer, agent, message: payload });
9855
9876
  }
9877
+ async function letGoOfOutsiders() {
9878
+ if (admittedOn.size === 0) return;
9879
+ const connected = new Set(connectedPeers());
9880
+ for (const [peer, { at }] of admittedOn) {
9881
+ if (!connected.has(peer) && Date.now() - at > STALE_ADMISSION_MS) admittedOn.delete(peer);
9882
+ }
9883
+ const allowed = readers((await access()).history.current);
9884
+ for (const peer of connected) {
9885
+ const admission = admittedOn.get(peer);
9886
+ if (!admission || admission.readKey === currentReadKey) continue;
9887
+ if (!peerAccounts.has(peer) && Date.now() - admission.at < WHO_WAIT_MS) continue;
9888
+ const { account } = await peerAccounts.get(peer) ?? NO_ACCOUNT;
9889
+ if (account !== null && allowed.has(account)) continue;
9890
+ if (admittedOn.get(peer) !== admission) continue;
9891
+ admittedOn.delete(peer);
9892
+ for (const network of networks) network.drop(peer);
9893
+ }
9894
+ }
9856
9895
  for (const network of networks) {
9857
9896
  network.on("message", (message) => {
9858
9897
  if (message.type === "sync") void sync.handleMessage(message.from, message.payload);
@@ -9864,6 +9903,8 @@ async function openSpaceRuntime(deps) {
9864
9903
  if (peerAccounts.get(message.from) !== checked) return;
9865
9904
  knownAccounts.set(message.from, known);
9866
9905
  emit({ type: "status", space: space2.id });
9906
+ void letGoOfOutsiders().catch(() => {
9907
+ });
9867
9908
  });
9868
9909
  } else if (message.type === LIVE_MESSAGE) void onLive(message.from, message.payload);
9869
9910
  });
@@ -9873,6 +9914,8 @@ async function openSpaceRuntime(deps) {
9873
9914
  sync.addPeer(info.did);
9874
9915
  sync.notifyPeers([info.did]);
9875
9916
  emit({ type: "status", space: space2.id });
9917
+ void letGoOfOutsiders().catch(() => {
9918
+ });
9876
9919
  });
9877
9920
  network.on("peer-disconnected", (info) => {
9878
9921
  if (routes.get(info.did) === network) {
@@ -9882,6 +9925,7 @@ async function openSpaceRuntime(deps) {
9882
9925
  knownAccounts.delete(info.did);
9883
9926
  liveAllowance.delete(info.did);
9884
9927
  }
9928
+ if (!connectedPeers().includes(info.did)) admittedOn.delete(info.did);
9885
9929
  emit({ type: "status", space: space2.id });
9886
9930
  });
9887
9931
  network.on("error", () => {
@@ -12942,7 +12986,8 @@ var app = {
12942
12986
  title: { type: "string", minLength: 1, maxLength: 100 },
12943
12987
  description: { type: "string", maxLength: 1e3 },
12944
12988
  needs: { type: "array", minItems: 1, maxItems: MAX_APP_COLLECTIONS, items: { type: "object" } },
12945
- from: { type: "string", maxLength: 300 }
12989
+ from: { type: "string", maxLength: 300 },
12990
+ updates: { type: "string", minLength: 1, maxLength: 100 }
12946
12991
  },
12947
12992
  required: ["title", "needs"]
12948
12993
  },
@@ -12956,6 +13001,9 @@ function checkApp(value) {
12956
13001
  if (body.description !== void 0 && (typeof body.description !== "string" || body.description.length > 1e3)) {
12957
13002
  return "An app's description is at most 1000 characters";
12958
13003
  }
13004
+ if (body.updates !== void 0 && (typeof body.updates !== "string" || !body.updates || body.updates.length > 100)) {
13005
+ return `An app's "updates" is the key of the app it replaces`;
13006
+ }
12959
13007
  if (!Array.isArray(body.needs) || body.needs.length === 0 || body.needs.length > MAX_APP_COLLECTIONS) {
12960
13008
  return `An app needs 1\u2013${MAX_APP_COLLECTIONS} collections`;
12961
13009
  }
@@ -13031,9 +13079,25 @@ function reviewApp(body, collections) {
13031
13079
  });
13032
13080
  return { needs, added: needs.every((need) => need.status === "same"), problem: null };
13033
13081
  }
13082
+ function supersededApps(records, collections) {
13083
+ const replaces = /* @__PURE__ */ new Map();
13084
+ for (const record of records) {
13085
+ if (record.body?.updates && record.body.updates !== record.key) replaces.set(record.key, record.body.updates);
13086
+ }
13087
+ const superseded = /* @__PURE__ */ new Set();
13088
+ for (const record of records) {
13089
+ if (!record.body || !reviewApp(record.body, collections).added) continue;
13090
+ for (let key3 = replaces.get(record.key); key3 && !superseded.has(key3) && key3 !== record.key; key3 = replaces.get(key3)) superseded.add(key3);
13091
+ }
13092
+ return superseded;
13093
+ }
13034
13094
  async function proposeApp(node, spaceId, body) {
13035
13095
  const problem = checkApp(body);
13036
13096
  if (problem) throw new Error(problem);
13097
+ if (body.updates) {
13098
+ const replaced = await node.records.get(spaceId, body.updates);
13099
+ if (!replaced || replaced.collection !== app.name) throw new Error(`"updates" names no app in this space: ${body.updates}`);
13100
+ }
13037
13101
  return node.records.put(spaceId, app.name, body);
13038
13102
  }
13039
13103
 
@@ -13280,7 +13344,7 @@ var NODE_ACTIONS = Object.freeze([
13280
13344
  },
13281
13345
  {
13282
13346
  name: "apps_list",
13283
- description: 'The apps proposed in a space: each with its title, who proposed it (viaAgent when an agent did), and for every collection it needs whether it is new, already there, or would change one \u2014 with what it allows, worked out from its rules. "added" is true once a person has added it.',
13347
+ description: 'The apps proposed in a space: each with its title, who proposed it (viaAgent when an agent did), and for every collection it needs whether it is new, already there, or would change one \u2014 with what it allows, worked out from its rules. "added" is true once a person has added it. "updates" names the app it is a new version of; "superseded" is true once a newer version of it has been added, so it is only history.',
13284
13348
  input: { type: "object", properties: { space }, required: ["space"] },
13285
13349
  readOnly: true,
13286
13350
  peerContent: true,
@@ -13288,6 +13352,7 @@ var NODE_ACTIONS = Object.freeze([
13288
13352
  const spaceId = str(input, "space");
13289
13353
  const collections = await node.collections.list(spaceId);
13290
13354
  const found = await node.records.list(spaceId, { collection: app.name });
13355
+ const superseded = supersededApps(found, collections);
13291
13356
  return found.map((record) => {
13292
13357
  const review = record.body ? reviewApp(record.body, collections) : null;
13293
13358
  return {
@@ -13297,7 +13362,9 @@ var NODE_ACTIONS = Object.freeze([
13297
13362
  proposedBy: record.createdBy,
13298
13363
  ...record.viaAgent ? { viaAgent: true } : {},
13299
13364
  ...record.body && appScreen(record.body) ? { screen: appScreen(record.body).collection } : {},
13365
+ ...record.body?.updates ? { updates: record.body.updates } : {},
13300
13366
  added: review?.added ?? false,
13367
+ superseded: superseded.has(record.key),
13301
13368
  problem: review?.problem ?? (record.body ? null : "It could not be read"),
13302
13369
  needs: review?.needs.map(({ definition, status, summary, changes }) => ({ name: definition.name, status, summary, changes })) ?? []
13303
13370
  };
@@ -13313,13 +13380,14 @@ var NODE_ACTIONS = Object.freeze([
13313
13380
  },
13314
13381
  {
13315
13382
  name: "apps_propose",
13316
- description: `Propose an app in a space: a new way for its people to work together \u2014 a carpool, a sign-up sheet, a decision log. Give it a title, a line on what it is for, and the collections it needs, each exactly as collections_define takes it (name, title, description, schema, links, permissions, rules \u2014 no version). Nothing is defined yet: everyone in the space sees the proposal, with what it allows worked out from its rules, and a person who may define collections adds it. Read collections_list first and reuse what the space already has (std.poll, std.task\u2026) rather than inventing a twin. For anything plain lists and forms can't show \u2014 a game board, a calendar, a whiteboard \u2014 give the main collection a "screen": its own HTML UI (one document, inline scripts and styles, no network unless the collection names exact origins in "network"). Inside it, use exactly: weave.me ({ did, name }), await weave.list("<collection>", { where: { "link:<rel>": key } }), await weave.put("<collection>", body, { links: [{ rel, to: key }] }), await weave.update(key, body), await weave.remove(key), weave.onChange(redraw). Records are { key, body, links, createdBy, mine }. Read apps_screen_guide for the rest. Returns what each collection will allow; tell the person that, not your own description.`,
13383
+ description: `Propose an app in a space: a new way for its people to work together \u2014 a carpool, a sign-up sheet, a decision log. Give it a title, a line on what it is for, and the collections it needs, each exactly as collections_define takes it (name, title, description, schema, links, permissions, rules \u2014 no version). Nothing is defined yet: everyone in the space sees the proposal, with what it allows worked out from its rules, and a person who may define collections adds it. Read collections_list first and reuse what the space already has (std.poll, std.task\u2026) rather than inventing a twin. To change an app that is already here, read apps_list and pass the key of its newest version, the one not superseded, as "updates": the change then shows as an update to it, and once it is added the old version is not offered again. For anything plain lists and forms can't show \u2014 a game board, a calendar, a whiteboard \u2014 give the main collection a "screen": its own HTML UI (one document, inline scripts and styles, no network unless the collection names exact origins in "network"). Inside it, use exactly: weave.me ({ did, name }), await weave.list("<collection>", { where: { "link:<rel>": key } }), await weave.put("<collection>", body, { links: [{ rel, to: key }] }), await weave.update(key, body), await weave.remove(key), weave.onChange(redraw). Records are { key, body, links, createdBy, mine }. Read apps_screen_guide for the rest. Returns what each collection will allow; tell the person that, not your own description.`,
13317
13384
  input: {
13318
13385
  type: "object",
13319
13386
  properties: {
13320
13387
  space,
13321
13388
  title: { type: "string", description: 'What people will call it, e.g. "Carpool"' },
13322
13389
  description: { type: "string", description: "What it is for, in a sentence" },
13390
+ updates: { type: "string", description: "The key of the app this is a new version of, from apps_list" },
13323
13391
  needs: {
13324
13392
  type: "array",
13325
13393
  description: "Collection definitions, as collections_define takes them \u2014 without version",
@@ -13334,6 +13402,7 @@ var NODE_ACTIONS = Object.freeze([
13334
13402
  const body = {
13335
13403
  title: str(input, "title"),
13336
13404
  ...typeof input.description === "string" ? { description: input.description } : {},
13405
+ ...typeof input.updates === "string" ? { updates: input.updates } : {},
13337
13406
  needs: input.needs
13338
13407
  };
13339
13408
  const problem = checkApp(body);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weaveprotocol/cli",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "The Weave protocol from a terminal: manage spaces, run an always-on node, serve MCP to agents",
5
5
  "type": "module",
6
6
  "bin": {