@weaveprotocol/cli 0.2.1 → 0.2.3

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 (3) hide show
  1. package/README.md +2 -2
  2. package/dist/weave.mjs +196 -114
  3. package/package.json +11 -6
package/README.md CHANGED
@@ -30,7 +30,7 @@ To have `weave` everywhere:
30
30
  npm install -g @weaveprotocol/cli # or run it without installing: npx @weaveprotocol/cli <command>
31
31
  ```
32
32
 
33
- From this repo: `npm install` at the repo root (the CLI is a workspace), then `cd cli && npm run bundle && npm link`, or a
33
+ From this repo: `npm install` at the repo root (the CLI is a workspace), then `cd packages/cli && npm run bundle && npm link`, or a
34
34
  binary with no Node at all: `bun build.ts --native`.
35
35
 
36
36
  ```bash
@@ -230,5 +230,5 @@ space anywhere leaves it everywhere.
230
230
  across real networks, behind a strict NAT, in Safari, and on a phone
231
231
  switching apps.
232
232
 
233
- What the protocol still has planned is in [the spec](../docs/spec/README.md),
233
+ What the protocol still has planned is in [the spec](../../spec/README.md),
234
234
  under **Planned** in each part.
package/dist/weave.mjs CHANGED
@@ -6,7 +6,7 @@ import path6 from "node:path";
6
6
  import { createInterface as createInterface2 } from "node:readline";
7
7
  import { parseArgs } from "node:util";
8
8
 
9
- // ../src/utils/encoding.ts
9
+ // ../core/src/utils/encoding.ts
10
10
  var encoder = new TextEncoder();
11
11
  var decoder = new TextDecoder();
12
12
  function utf8Encode(str2) {
@@ -46,7 +46,7 @@ function bytesToHex(bytes) {
46
46
  return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
47
47
  }
48
48
 
49
- // ../node_modules/@noble/hashes/_u64.js
49
+ // ../../node_modules/@noble/hashes/_u64.js
50
50
  var fromNumH = (n) => n / 2 ** 32 | 0;
51
51
  var fromNumL = (n) => n >>> 0;
52
52
  function setU64FromNum(view, byteOffset, n, isLE) {
@@ -56,7 +56,7 @@ function setU64FromNum(view, byteOffset, n, isLE) {
56
56
  view.setUint32(byteOffset + 4, isLE ? h : l, isLE);
57
57
  }
58
58
 
59
- // ../node_modules/@noble/hashes/utils.js
59
+ // ../../node_modules/@noble/hashes/utils.js
60
60
  function isBytes(a) {
61
61
  return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array" && "BYTES_PER_ELEMENT" in a && a.BYTES_PER_ELEMENT === 1;
62
62
  }
@@ -221,7 +221,7 @@ var oidNist = (suffix) => ({
221
221
  oid: Uint8Array.from([6, 9, 96, 134, 72, 1, 101, 3, 4, 2, suffix])
222
222
  });
223
223
 
224
- // ../node_modules/@noble/hashes/_md.js
224
+ // ../../node_modules/@noble/hashes/_md.js
225
225
  function Chi(a, b, c) {
226
226
  return a & b ^ ~a & c;
227
227
  }
@@ -334,7 +334,7 @@ var SHA256_IV = /* @__PURE__ */ Uint32Array.from([
334
334
  1541459225
335
335
  ]);
336
336
 
337
- // ../node_modules/@noble/hashes/sha2.js
337
+ // ../../node_modules/@noble/hashes/sha2.js
338
338
  var SHA256_K = /* @__PURE__ */ Uint32Array.from([
339
339
  1116352408,
340
340
  1899447441,
@@ -499,7 +499,7 @@ var sha256 = /* @__PURE__ */ createHasher(
499
499
  /* @__PURE__ */ oidNist(1)
500
500
  );
501
501
 
502
- // ../node_modules/@noble/curves/utils.js
502
+ // ../../node_modules/@noble/curves/utils.js
503
503
  function aarray(item, title, inner = () => {
504
504
  }) {
505
505
  if (!Array.isArray(item))
@@ -681,7 +681,7 @@ function validateObject(object, fields = {}, optFields = {}, title = "object") {
681
681
  iter(optFields, true);
682
682
  }
683
683
 
684
- // ../node_modules/@noble/curves/abstract/modular.js
684
+ // ../../node_modules/@noble/curves/abstract/modular.js
685
685
  var _0n2 = /* @__PURE__ */ BigInt(0);
686
686
  var _1n2 = /* @__PURE__ */ BigInt(1);
687
687
  var _2n = /* @__PURE__ */ BigInt(2);
@@ -1131,7 +1131,7 @@ function mapHashToField(key3, fieldOrder, isLE = false) {
1131
1131
  return isLE ? numberToBytesLE(reduced, fieldLen) : numberToBytesBE(reduced, fieldLen);
1132
1132
  }
1133
1133
 
1134
- // ../node_modules/@noble/curves/abstract/curve.js
1134
+ // ../../node_modules/@noble/curves/abstract/curve.js
1135
1135
  var _0n3 = /* @__PURE__ */ BigInt(0);
1136
1136
  var _1n3 = /* @__PURE__ */ BigInt(1);
1137
1137
  var _4n2 = /* @__PURE__ */ BigInt(4);
@@ -1518,7 +1518,7 @@ function createKeygen(randomSecretKey, getPublicKey) {
1518
1518
  };
1519
1519
  }
1520
1520
 
1521
- // ../node_modules/@noble/hashes/hmac.js
1521
+ // ../../node_modules/@noble/hashes/hmac.js
1522
1522
  var _HMAC = class {
1523
1523
  oHash;
1524
1524
  iHash;
@@ -1595,7 +1595,7 @@ var hmac = /* @__PURE__ */ (() => {
1595
1595
  return hmac_;
1596
1596
  })();
1597
1597
 
1598
- // ../node_modules/@noble/curves/abstract/der.js
1598
+ // ../../node_modules/@noble/curves/abstract/der.js
1599
1599
  var _0n4 = /* @__PURE__ */ BigInt(0);
1600
1600
  var DERErr = class extends Error {
1601
1601
  constructor(m = "") {
@@ -1722,7 +1722,7 @@ var DER = /* @__PURE__ */ (() => {
1722
1722
  return Object.freeze(_DER);
1723
1723
  })();
1724
1724
 
1725
- // ../node_modules/@noble/curves/abstract/weierstrass.js
1725
+ // ../../node_modules/@noble/curves/abstract/weierstrass.js
1726
1726
  var divNearest = (num, den) => (num + (num >= 0 ? den : -den) / _2n2) / den;
1727
1727
  function _splitEndoScalar(k, basis, n) {
1728
1728
  aInRange("scalar", k, _0n5, n);
@@ -2554,7 +2554,7 @@ function ecdsa(Point, hash2, ecdsaOpts = {}) {
2554
2554
  });
2555
2555
  }
2556
2556
 
2557
- // ../node_modules/@noble/curves/nist.js
2557
+ // ../../node_modules/@noble/curves/nist.js
2558
2558
  var p256_CURVE = /* @__PURE__ */ (() => ({
2559
2559
  p: BigInt("0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff"),
2560
2560
  n: BigInt("0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551"),
@@ -2567,7 +2567,7 @@ var p256_CURVE = /* @__PURE__ */ (() => ({
2567
2567
  var p256_Point = /* @__PURE__ */ weierstrass(p256_CURVE);
2568
2568
  var p256 = /* @__PURE__ */ ecdsa(p256_Point, sha256);
2569
2569
 
2570
- // ../src/identity/crypto-p256.ts
2570
+ // ../core/src/identity/crypto-p256.ts
2571
2571
  var P256_SEED_BYTES = 48;
2572
2572
  var P256_KEY_INFO = new TextEncoder().encode("weave/p256-identity-key/v1");
2573
2573
  async function hkdf(ikm, info, length) {
@@ -2679,12 +2679,12 @@ function createP256Provider() {
2679
2679
  });
2680
2680
  }
2681
2681
 
2682
- // ../src/utils/errors.ts
2682
+ // ../core/src/utils/errors.ts
2683
2683
  function protocolError(code, message, hint) {
2684
2684
  return Object.assign(new Error(message), { code, ...hint ? { hint } : {} });
2685
2685
  }
2686
2686
 
2687
- // ../src/identity/webauthn.ts
2687
+ // ../core/src/identity/webauthn.ts
2688
2688
  var PRF_SALT = new TextEncoder().encode("weave-protocol-key-v1");
2689
2689
  async function registerPasskey(options) {
2690
2690
  if (!globalThis.navigator?.credentials) {
@@ -2780,10 +2780,10 @@ async function authenticatePasskey(credentialId, options) {
2780
2780
  });
2781
2781
  }
2782
2782
 
2783
- // ../src/identity/passkey-diagnostics.ts
2783
+ // ../core/src/identity/passkey-diagnostics.ts
2784
2784
  var PRF_SALT2 = new TextEncoder().encode("weave-protocol-key-v1");
2785
2785
 
2786
- // ../src/identity/keys.ts
2786
+ // ../core/src/identity/keys.ts
2787
2787
  async function deriveKeyPair(seed, provider) {
2788
2788
  const keyPair = await provider.deriveKeyPairFromSeed(seed);
2789
2789
  const publicKeyBytes = await provider.exportPublicKey(keyPair.publicKey);
@@ -2815,7 +2815,7 @@ async function deriveKeyFromPassword(password, salt, provider) {
2815
2815
  return deriveKeyPair(new Uint8Array(derivedBits), provider);
2816
2816
  }
2817
2817
 
2818
- // ../src/identity/recovery-code.ts
2818
+ // ../core/src/identity/recovery-code.ts
2819
2819
  var ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
2820
2820
  var SUBSTITUTIONS = { O: "0", I: "1", L: "1", U: "V" };
2821
2821
  var SEED_BYTES = 16;
@@ -2876,7 +2876,7 @@ function encodeBase32(bytes) {
2876
2876
  return out;
2877
2877
  }
2878
2878
 
2879
- // ../node_modules/@scure/base/index.js
2879
+ // ../../node_modules/@scure/base/index.js
2880
2880
  var freeze = (fn) => Object.freeze(fn());
2881
2881
  function isBytes3(a) {
2882
2882
  return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array" && "BYTES_PER_ELEMENT" in a && a.BYTES_PER_ELEMENT === 1;
@@ -3082,7 +3082,7 @@ var radix58 = /* @__PURE__ */ radixBaseN(58, B58_GROUP);
3082
3082
  var genBase58 = (abc) => genBaseN(radix58, abc);
3083
3083
  var base58 = /* @__PURE__ */ freeze(() => genBase58("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"));
3084
3084
 
3085
- // ../src/identity/did.ts
3085
+ // ../core/src/identity/did.ts
3086
3086
  var P256_MULTICODEC = new Uint8Array([128, 36]);
3087
3087
  function publicKeyToDid(publicKeyBytes, multicodecPrefix) {
3088
3088
  const prefixedKey = concatBytes(multicodecPrefix, publicKeyBytes);
@@ -3103,7 +3103,7 @@ function didToPublicKey(did) {
3103
3103
  return { multicodecPrefix, publicKeyBytes };
3104
3104
  }
3105
3105
 
3106
- // ../src/identity/identity-manager.ts
3106
+ // ../core/src/identity/identity-manager.ts
3107
3107
  function prfUnsupported() {
3108
3108
  return protocolError(
3109
3109
  "PRF_UNSUPPORTED",
@@ -3194,7 +3194,7 @@ function createIdentityManager(config) {
3194
3194
  });
3195
3195
  }
3196
3196
 
3197
- // ../src/storage/folder-adapter.ts
3197
+ // ../core/src/storage/folder-adapter.ts
3198
3198
  var EXPRESSION_SUFFIX = ".json";
3199
3199
  function encodeKey(key3) {
3200
3200
  let out = "";
@@ -3365,7 +3365,7 @@ async function createFolderAdapter(root, path7) {
3365
3365
  });
3366
3366
  }
3367
3367
 
3368
- // ../src/identity/folder-account.ts
3368
+ // ../core/src/identity/folder-account.ts
3369
3369
  function createVault(params) {
3370
3370
  return {
3371
3371
  version: 2,
@@ -3376,7 +3376,7 @@ function createVault(params) {
3376
3376
  };
3377
3377
  }
3378
3378
 
3379
- // ../src/identity/account-store.ts
3379
+ // ../core/src/identity/account-store.ts
3380
3380
  var LIST_FILE = "accounts.json";
3381
3381
  var ACCOUNTS_DIR = "accounts";
3382
3382
  var VAULT_FILE = "account.json";
@@ -3491,7 +3491,7 @@ function createFolderAccountStore(dir) {
3491
3491
  });
3492
3492
  }
3493
3493
 
3494
- // ../src/identity/account-vault.ts
3494
+ // ../core/src/identity/account-vault.ts
3495
3495
  var IV_BYTES = 12;
3496
3496
  var SALT_BYTES = 16;
3497
3497
  var PASSPHRASE_ITERATIONS = 6e5;
@@ -3601,12 +3601,13 @@ async function deriveVaultKey(seed) {
3601
3601
  ["encrypt", "decrypt"]
3602
3602
  );
3603
3603
  }
3604
+ var CLI_PASSPHRASE_LABEL = "CLI passphrase";
3604
3605
  function withWrap(vault, wrap) {
3605
3606
  const superseded = (existing) => existing.kind === "device" && wrap.kind === "device" && existing.rpId === wrap.rpId;
3606
3607
  return { ...vault, wraps: [...vault.wraps.filter((existing) => !superseded(existing)), wrap] };
3607
3608
  }
3608
3609
 
3609
- // ../src/identity/contact-key.ts
3610
+ // ../core/src/identity/contact-key.ts
3610
3611
  var CONTACT_KEY_INFO = "weave/p256-contact-key/v1";
3611
3612
  var SEAL_INFO = "weave/contact-seal/v1";
3612
3613
  var MEMBER_KEY_INFO = "weave/p256-member-key/v1";
@@ -3749,7 +3750,7 @@ async function openSealed(privateKey, sealed, context) {
3749
3750
  }
3750
3751
  }
3751
3752
 
3752
- // ../src/utils/hash.ts
3753
+ // ../core/src/utils/hash.ts
3753
3754
  async function sha2562(data) {
3754
3755
  const buffer = await globalThis.crypto.subtle.digest("SHA-256", data);
3755
3756
  return new Uint8Array(buffer);
@@ -3800,7 +3801,7 @@ async function cidFromBytes(data) {
3800
3801
  return "b" + base32Encode(hashBytes);
3801
3802
  }
3802
3803
 
3803
- // ../src/records/links.ts
3804
+ // ../core/src/records/links.ts
3804
3805
  var LINK_REL_PATTERN = /^[a-z][a-zA-Z0-9]{0,63}$/;
3805
3806
  var MAX_LINKS = 32;
3806
3807
  function checkLinks(links2) {
@@ -3818,7 +3819,7 @@ function checkLinks(links2) {
3818
3819
  return null;
3819
3820
  }
3820
3821
 
3821
- // ../src/records/version.ts
3822
+ // ../core/src/records/version.ts
3822
3823
  var MAX_SEEN = 64;
3823
3824
  var RECORD_KEY_PATTERN = /^[a-z0-9:._-]{1,128}$/;
3824
3825
  function newRecordKey() {
@@ -3859,7 +3860,7 @@ function checkVersionShape(expression) {
3859
3860
  return null;
3860
3861
  }
3861
3862
 
3862
- // ../src/schema/expression.ts
3863
+ // ../core/src/schema/expression.ts
3863
3864
  function canonicalize(obj) {
3864
3865
  if (obj === null || typeof obj !== "object") {
3865
3866
  return JSON.stringify(obj) ?? "null";
@@ -3904,7 +3905,7 @@ async function getExpressionId(expr) {
3904
3905
  return await cidFromBytes(bytes);
3905
3906
  }
3906
3907
 
3907
- // ../src/identity/ucan.ts
3908
+ // ../core/src/identity/ucan.ts
3908
3909
  var UCAN_CLOCK_SKEW_SECONDS = 300;
3909
3910
  async function issueUCAN(options, provider) {
3910
3911
  const header = { alg: "ES256", typ: "JWT", ucv: "0.10.0" };
@@ -4095,7 +4096,7 @@ async function resolveDelegationRoot(encoded, resolveProof, provider, options =
4095
4096
  return { valid: false, rootDid: null, capabilities: [], audience: null, reason: `Delegation chain deeper than ${MAX_CHAIN_DEPTH} links` };
4096
4097
  }
4097
4098
 
4098
- // ../src/identity/root-signer.ts
4099
+ // ../core/src/identity/root-signer.ts
4099
4100
  function createLocalRootSigner(identity, provider) {
4100
4101
  return Object.freeze({
4101
4102
  did: identity.did,
@@ -4115,7 +4116,7 @@ function createLocalRootSigner(identity, provider) {
4115
4116
  });
4116
4117
  }
4117
4118
 
4118
- // ../src/identity/pairing.ts
4119
+ // ../core/src/identity/pairing.ts
4119
4120
  var ROOM_PREFIX = utf8Encode("weave-pairing-room-v1");
4120
4121
  var PAIRING_KEY_INFO = utf8Encode("weave-pairing-key-v1");
4121
4122
  var IV_BYTES3 = 12;
@@ -4147,7 +4148,7 @@ async function openPairingPayload(sealed, key3) {
4147
4148
  }
4148
4149
  }
4149
4150
 
4150
- // ../src/identity/agent-note.ts
4151
+ // ../core/src/identity/agent-note.ts
4151
4152
  var AGENT_FACT = Object.freeze({ weave: "agent" });
4152
4153
  function isAgentNote(encoded) {
4153
4154
  if (!encoded) return false;
@@ -4159,7 +4160,7 @@ function isAgentNote(encoded) {
4159
4160
  }
4160
4161
  }
4161
4162
 
4162
- // ../src/schema/schema-engine.ts
4163
+ // ../core/src/schema/schema-engine.ts
4163
4164
  function createSchemaEngine() {
4164
4165
  const collections = /* @__PURE__ */ new Map();
4165
4166
  return Object.freeze({
@@ -4192,7 +4193,7 @@ function createSchemaEngine() {
4192
4193
  });
4193
4194
  }
4194
4195
 
4195
- // ../src/schema/signer.ts
4196
+ // ../core/src/schema/signer.ts
4196
4197
  function createSigner(provider) {
4197
4198
  return Object.freeze({
4198
4199
  async sign(payload, privateKey) {
@@ -4218,7 +4219,7 @@ function createSigner(provider) {
4218
4219
  });
4219
4220
  }
4220
4221
 
4221
- // ../src/sync/negentropy.ts
4222
+ // ../core/src/sync/negentropy.ts
4222
4223
  var NEGENTROPY_VERSION = 97;
4223
4224
  var ID_SIZE = 32;
4224
4225
  var FINGERPRINT_SIZE = 16;
@@ -4508,7 +4509,7 @@ function createReconciler(set, options) {
4508
4509
  }
4509
4510
  var key = (id) => String.fromCharCode(...id);
4510
4511
 
4511
- // ../src/storage/storage-provider.ts
4512
+ // ../core/src/storage/storage-provider.ts
4512
4513
  var CURRENT_PREFIX = "r/";
4513
4514
  var GENESIS_PREFIX = "g/";
4514
4515
  var HISTORY_PREFIX = "h/";
@@ -4733,7 +4734,7 @@ function createStorageProvider(adapter) {
4733
4734
  });
4734
4735
  }
4735
4736
 
4736
- // ../src/storage/encrypted-adapter.ts
4737
+ // ../core/src/storage/encrypted-adapter.ts
4737
4738
  var MAGIC = new Uint8Array([119, 101, 97, 118, 101, 101, 2, 0]);
4738
4739
  var IV_BYTES4 = 12;
4739
4740
  var DEFAULT_ENCRYPTED_PREFIXES = ["space:", "spacekey:", "spaceinvite:", "spacerole:"];
@@ -4797,7 +4798,7 @@ function createEncryptedAdapter(inner, key3, options) {
4797
4798
  });
4798
4799
  }
4799
4800
 
4800
- // ../src/storage/folder-reconcile.ts
4801
+ // ../core/src/storage/folder-reconcile.ts
4801
4802
  async function reconcileFolder(storage, adapter, accept = async () => true) {
4802
4803
  const disk = await adapter.reload();
4803
4804
  if (disk.added.length > 0 || disk.removed.length > 0) storage.invalidate();
@@ -4826,7 +4827,7 @@ async function reconcileFolder(storage, adapter, accept = async () => true) {
4826
4827
  };
4827
4828
  }
4828
4829
 
4829
- // ../src/privacy/space-encryption.ts
4830
+ // ../core/src/privacy/space-encryption.ts
4830
4831
  async function generateSpaceKey() {
4831
4832
  const key3 = await globalThis.crypto.subtle.generateKey(
4832
4833
  { name: "AES-GCM", length: 256 },
@@ -4912,7 +4913,7 @@ async function openWith(spaceKey, sealed, context) {
4912
4913
  }
4913
4914
  }
4914
4915
 
4915
- // ../src/space/roles.ts
4916
+ // ../core/src/space/roles.ts
4916
4917
  var MANAGE = "manage";
4917
4918
  var INVITE = "invite";
4918
4919
  var DEFINE = "define";
@@ -5331,7 +5332,7 @@ function replayAccess(genesis, events) {
5331
5332
  }
5332
5333
  }
5333
5334
 
5334
- // ../src/space/space-access.ts
5335
+ // ../core/src/space/space-access.ts
5335
5336
  var READ_INFO = "weave/space-read/v1";
5336
5337
  var INVITE_INFO = "weave/space-invite/v1";
5337
5338
  function generateInviteSecret() {
@@ -5435,7 +5436,7 @@ async function verifyInvite(spaceId, did, inviteDid, signature, provider) {
5435
5436
  }
5436
5437
  }
5437
5438
 
5438
- // ../src/space/presets.ts
5439
+ // ../core/src/space/presets.ts
5439
5440
  var solo = Object.freeze({
5440
5441
  roles: Object.freeze([{ name: "owner", title: "Owner", rank: 100, permissions: ["*"] }]),
5441
5442
  creatorRole: "owner"
@@ -5457,7 +5458,7 @@ var community = Object.freeze({
5457
5458
  });
5458
5459
  var rolePresets = Object.freeze({ solo, team, community });
5459
5460
 
5460
- // ../src/space/space-manager.ts
5461
+ // ../core/src/space/space-manager.ts
5461
5462
  var SPACE_PREFIX = "space:";
5462
5463
  var KEY_PREFIX = "spacekey:";
5463
5464
  var INVITE_PREFIX = "spaceinvite:";
@@ -5635,7 +5636,7 @@ function parseSpaceInvite(invite) {
5635
5636
  }
5636
5637
  }
5637
5638
 
5638
- // ../src/space/account-registry.ts
5639
+ // ../core/src/space/account-registry.ts
5639
5640
  var MEMBERSHIP_COLLECTION = "sys.joined";
5640
5641
  var PROFILE_COLLECTION = "sys.profile";
5641
5642
  var PROFILE_KEY = "profile";
@@ -5687,7 +5688,7 @@ async function deriveAccountSpace(accountKey, owner, label, name, provider) {
5687
5688
  return { space: space2, key: key3, invite: null, role: solo.creatorRole };
5688
5689
  }
5689
5690
 
5690
- // ../node_modules/@cfworker/json-schema/dist/esm/deep-compare-strict.js
5691
+ // ../../node_modules/@cfworker/json-schema/dist/esm/deep-compare-strict.js
5691
5692
  function deepCompareStrict(a, b) {
5692
5693
  const typeofa = typeof a;
5693
5694
  if (typeofa !== typeof b) {
@@ -5728,7 +5729,7 @@ function deepCompareStrict(a, b) {
5728
5729
  return a === b;
5729
5730
  }
5730
5731
 
5731
- // ../node_modules/@cfworker/json-schema/dist/esm/pointer.js
5732
+ // ../../node_modules/@cfworker/json-schema/dist/esm/pointer.js
5732
5733
  function encodePointer(p) {
5733
5734
  return encodeURI(escapePointer(p));
5734
5735
  }
@@ -5736,7 +5737,7 @@ function escapePointer(p) {
5736
5737
  return p.replace(/~/g, "~0").replace(/\//g, "~1");
5737
5738
  }
5738
5739
 
5739
- // ../node_modules/@cfworker/json-schema/dist/esm/dereference.js
5740
+ // ../../node_modules/@cfworker/json-schema/dist/esm/dereference.js
5740
5741
  var schemaArrayKeyword = {
5741
5742
  prefixItems: true,
5742
5743
  items: true,
@@ -5857,7 +5858,7 @@ function dereference(schema, lookup = /* @__PURE__ */ Object.create(null), baseU
5857
5858
  return lookup;
5858
5859
  }
5859
5860
 
5860
- // ../node_modules/@cfworker/json-schema/dist/esm/format.js
5861
+ // ../../node_modules/@cfworker/json-schema/dist/esm/format.js
5861
5862
  var DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/;
5862
5863
  var DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
5863
5864
  var TIME = /^(\d\d):(\d\d):(\d\d)(\.\d+)?(z|[+-]\d\d(?::?\d\d)?)?$/i;
@@ -5950,7 +5951,7 @@ function regex(str2) {
5950
5951
  }
5951
5952
  }
5952
5953
 
5953
- // ../node_modules/@cfworker/json-schema/dist/esm/types.js
5954
+ // ../../node_modules/@cfworker/json-schema/dist/esm/types.js
5954
5955
  var OutputFormat;
5955
5956
  (function(OutputFormat2) {
5956
5957
  OutputFormat2[OutputFormat2["Flag"] = 1] = "Flag";
@@ -5958,7 +5959,7 @@ var OutputFormat;
5958
5959
  OutputFormat2[OutputFormat2["Detailed"] = 4] = "Detailed";
5959
5960
  })(OutputFormat || (OutputFormat = {}));
5960
5961
 
5961
- // ../node_modules/@cfworker/json-schema/dist/esm/ucs2-length.js
5962
+ // ../../node_modules/@cfworker/json-schema/dist/esm/ucs2-length.js
5962
5963
  function ucs2length(s) {
5963
5964
  let result = 0;
5964
5965
  let length = s.length;
@@ -5977,7 +5978,7 @@ function ucs2length(s) {
5977
5978
  return result;
5978
5979
  }
5979
5980
 
5980
- // ../node_modules/@cfworker/json-schema/dist/esm/validate.js
5981
+ // ../../node_modules/@cfworker/json-schema/dist/esm/validate.js
5981
5982
  function validate(instance, schema, draft = "2019-09", lookup = dereference(schema), shortCircuit = true, recursiveAnchor = null, instanceLocation = "#", schemaLocation = "#", evaluated = /* @__PURE__ */ Object.create(null)) {
5982
5983
  if (schema === true) {
5983
5984
  return { valid: true, errors: [] };
@@ -6735,7 +6736,7 @@ Known schemas:
6735
6736
  return { valid: errors.length === 0, errors };
6736
6737
  }
6737
6738
 
6738
- // ../node_modules/@cfworker/json-schema/dist/esm/validator.js
6739
+ // ../../node_modules/@cfworker/json-schema/dist/esm/validator.js
6739
6740
  var Validator = class {
6740
6741
  schema;
6741
6742
  draft;
@@ -6758,7 +6759,7 @@ var Validator = class {
6758
6759
  }
6759
6760
  };
6760
6761
 
6761
- // ../src/records/rules.ts
6762
+ // ../core/src/records/rules.ts
6762
6763
  var PERMISSION_PATTERN = /^[a-z][a-zA-Z0-9]{0,39}$/;
6763
6764
  var permissionName = (collection2, permission) => `${collection2}/${permission}`;
6764
6765
  var isWho = (w) => w === "member" || w === "creator" || typeof w === "string" && w.startsWith("can:") && PERMISSION_PATTERN.test(w.slice(4));
@@ -6828,7 +6829,7 @@ function changedFixedField(fixed, first, next) {
6828
6829
  return null;
6829
6830
  }
6830
6831
 
6831
- // ../src/records/topics.ts
6832
+ // ../core/src/records/topics.ts
6832
6833
  var MAX_TOPICS = 8;
6833
6834
  var MAX_TAGS = 64;
6834
6835
  var TAG_BYTES = 16;
@@ -6880,7 +6881,7 @@ function sameTags(a, b) {
6880
6881
  return left.length === right.length && left.every((tag, i) => tag === right[i]);
6881
6882
  }
6882
6883
 
6883
- // ../src/schema/collection-def.ts
6884
+ // ../core/src/schema/collection-def.ts
6884
6885
  function toJsonSchema(schema) {
6885
6886
  const standard = schema["~standard"];
6886
6887
  if (!standard) return schema;
@@ -6893,6 +6894,21 @@ function toJsonSchema(schema) {
6893
6894
  return json;
6894
6895
  }
6895
6896
  var MAX_SCREEN_BYTES = 48 * 1024;
6897
+ var MAX_SCREEN_ORIGINS = 8;
6898
+ var SCREEN_ORIGIN = /^(https|wss):\/\/[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+(:[0-9]{1,5})?$/;
6899
+ function checkScreenNetwork(network, hasScreen) {
6900
+ if (network === void 0) return null;
6901
+ if (!Array.isArray(network)) return 'network must be a list of origins, like ["https://api.example.com"]';
6902
+ if (!hasScreen && network.length) return "network is where a screen may connect; this definition has no screen";
6903
+ if (network.length > MAX_SCREEN_ORIGINS) return `network may name at most ${MAX_SCREEN_ORIGINS} origins`;
6904
+ for (const origin of network) {
6905
+ if (typeof origin !== "string" || !SCREEN_ORIGIN.test(origin)) {
6906
+ return `"${String(origin)}" is not an origin a screen may reach: give https:// or wss:// and a host, lower case, no path \u2014 like "https://api.example.com"`;
6907
+ }
6908
+ }
6909
+ if (new Set(network).size !== network.length) return "network names an origin twice";
6910
+ return null;
6911
+ }
6896
6912
  var CATALOG_COLLECTION = "sys.collection";
6897
6913
  var KEYWORDS = /* @__PURE__ */ new Set([
6898
6914
  "type",
@@ -7029,6 +7045,8 @@ function checkStoredCollection(definition) {
7029
7045
  const bytes = new TextEncoder().encode(d.screen).length;
7030
7046
  if (bytes > MAX_SCREEN_BYTES) return `screen is ${Math.ceil(bytes / 1024)} KB; at most ${MAX_SCREEN_BYTES / 1024} KB`;
7031
7047
  }
7048
+ const network = checkScreenNetwork(d.network, typeof d.screen === "string");
7049
+ if (network) return network;
7032
7050
  return checkPublishableSchema(d.schema);
7033
7051
  }
7034
7052
  function validateJsonSchema(schema, value) {
@@ -7041,7 +7059,7 @@ function validateJsonSchema(schema, value) {
7041
7059
  }));
7042
7060
  }
7043
7061
 
7044
- // ../src/utils/events.ts
7062
+ // ../core/src/utils/events.ts
7045
7063
  function createEmitter() {
7046
7064
  const listeners = /* @__PURE__ */ new Map();
7047
7065
  return {
@@ -7063,7 +7081,7 @@ function createEmitter() {
7063
7081
  };
7064
7082
  }
7065
7083
 
7066
- // ../src/network/signaling.ts
7084
+ // ../core/src/network/signaling.ts
7067
7085
  var SIGNAL_KINDS = /* @__PURE__ */ new Set(["offer", "answer", "candidate"]);
7068
7086
  function iceFrom(payload) {
7069
7087
  const { servers, expiresAt } = payload ?? {};
@@ -7180,7 +7198,7 @@ function createSignalingClient(url, did) {
7180
7198
  });
7181
7199
  }
7182
7200
 
7183
- // ../src/network/multi-signaling.ts
7201
+ // ../core/src/network/multi-signaling.ts
7184
7202
  function createMultiSignalingClient(urls, did) {
7185
7203
  if (urls.length === 0) {
7186
7204
  throw new Error("At least one relay is needed to introduce peers.");
@@ -7314,7 +7332,7 @@ function createMultiSignalingClient(urls, did) {
7314
7332
  });
7315
7333
  }
7316
7334
 
7317
- // ../src/network/introductions.ts
7335
+ // ../core/src/network/introductions.ts
7318
7336
  var PEERS_MESSAGE = "__peers";
7319
7337
  var SIGNAL_MESSAGE = "__signal";
7320
7338
  var AUTH_HELLO_MESSAGE = "__auth-hello";
@@ -7353,7 +7371,7 @@ function signalId() {
7353
7371
  return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
7354
7372
  }
7355
7373
 
7356
- // ../src/network/rtc-transport.ts
7374
+ // ../core/src/network/rtc-transport.ts
7357
7375
  function fingerprintOf2(description) {
7358
7376
  const match = description?.sdp?.match(/^a=fingerprint:\s*(\S+)\s+(\S+)/im);
7359
7377
  return match ? `${match[1].toLowerCase()} ${match[2].toUpperCase()}` : null;
@@ -7489,7 +7507,8 @@ function createRTCTransport(config) {
7489
7507
  });
7490
7508
  }
7491
7509
 
7492
- // ../src/network/peer-auth.ts
7510
+ // ../core/src/network/peer-auth.ts
7511
+ var MAX_EARLIER_READ_KEYS = 4;
7493
7512
  function readAccessOf(read) {
7494
7513
  if (!read) return null;
7495
7514
  if ("current" in read) return read;
@@ -7500,16 +7519,22 @@ async function proveRead(access, label, provider) {
7500
7519
  const key3 = await access.key();
7501
7520
  if (!key3) return null;
7502
7521
  const read = base64UrlEncode(await provider.sign(key3.privateKey, label));
7503
- if (key3.did === access.current()) return { read };
7522
+ if (!key3.did) return { read };
7504
7523
  const member = await access.membership?.() ?? null;
7505
- return { read, readKey: key3.did, ...member ? { member } : {} };
7524
+ const older = (await access.earlier?.() ?? []).filter((held) => held.did !== key3.did).slice(0, MAX_EARLIER_READ_KEYS);
7525
+ const earlier = await Promise.all(older.map(async (held) => ({ readKey: held.did, read: base64UrlEncode(await provider.sign(held.privateKey, label)) })));
7526
+ return { read, readKey: key3.did, ...member ? { member } : {}, ...earlier.length ? { earlier } : {} };
7506
7527
  }
7507
7528
  async function checkRead(access, peerDid, label, proof, provider) {
7508
7529
  const current = access.current();
7509
7530
  const claimed = typeof proof.readKey === "string" ? proof.readKey : current;
7510
- if (!claimed.startsWith("did:key:") || !await verifyBy(provider, claimed, proof.read, label)) return false;
7511
- if (claimed === current) return true;
7512
- return access.admits ? access.admits(peerDid, claimed, proof.member) : false;
7531
+ 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;
7534
+ }
7535
+ const earlier = Array.isArray(proof.earlier) ? proof.earlier.slice(0, MAX_EARLIER_READ_KEYS) : [];
7536
+ const ours = earlier.find((item) => item?.readKey === current);
7537
+ return ours !== void 0 && current.startsWith("did:key:") && verifyBy(provider, current, ours.read, label);
7513
7538
  }
7514
7539
  function peerNonce() {
7515
7540
  return base64UrlEncode(globalThis.crypto.getRandomValues(new Uint8Array(16)));
@@ -7575,7 +7600,7 @@ function createMeshAuth(spaceId, session, read, provider) {
7575
7600
  });
7576
7601
  }
7577
7602
 
7578
- // ../src/network/ws-transport.ts
7603
+ // ../core/src/network/ws-transport.ts
7579
7604
  function parseFrame(data, type) {
7580
7605
  if (typeof data !== "string") return null;
7581
7606
  try {
@@ -7721,7 +7746,7 @@ function createWebSocketTransport(config) {
7721
7746
  });
7722
7747
  }
7723
7748
 
7724
- // ../src/network/mesh.ts
7749
+ // ../core/src/network/mesh.ts
7725
7750
  var ICE_REFRESH_MS = 10 * 6e4;
7726
7751
  var ICE_WAIT_MS = 1500;
7727
7752
  function createMesh(config) {
@@ -8002,7 +8027,7 @@ function createMesh(config) {
8002
8027
  };
8003
8028
  }
8004
8029
 
8005
- // ../src/network/network-manager.ts
8030
+ // ../core/src/network/network-manager.ts
8006
8031
  function createNetworkManager(config) {
8007
8032
  const transport = config.createTransport();
8008
8033
  const peers = /* @__PURE__ */ new Map();
@@ -8048,7 +8073,7 @@ function createNetworkManager(config) {
8048
8073
  });
8049
8074
  }
8050
8075
 
8051
- // ../src/sync/sync-messages.ts
8076
+ // ../core/src/sync/sync-messages.ts
8052
8077
  var SYNC_PROTOCOL_VERSION = 4;
8053
8078
  function parseSyncMessage(value) {
8054
8079
  const message = value;
@@ -8056,7 +8081,7 @@ function parseSyncMessage(value) {
8056
8081
  return message;
8057
8082
  }
8058
8083
 
8059
- // ../src/sync/sync-engine.ts
8084
+ // ../core/src/sync/sync-engine.ts
8060
8085
  var MAX_WAITING = 1e3;
8061
8086
  var MAX_REFUSED = 1e4;
8062
8087
  var holdsCollection = (holds2, collection2) => collection2.startsWith("sys.") || holds2 === "all" || holds2.has(collection2);
@@ -8344,7 +8369,7 @@ ${id}`;
8344
8369
  };
8345
8370
  }
8346
8371
 
8347
- // ../src/validation/crypto-gate.ts
8372
+ // ../core/src/validation/crypto-gate.ts
8348
8373
  function createCryptoGate(provider) {
8349
8374
  return {
8350
8375
  async validate(expression, resolvePublicKey) {
@@ -8371,7 +8396,7 @@ function createCryptoGate(provider) {
8371
8396
  };
8372
8397
  }
8373
8398
 
8374
- // ../src/validation/structural-gate.ts
8399
+ // ../core/src/validation/structural-gate.ts
8375
8400
  function isEncryptedBody(body) {
8376
8401
  if (!body || typeof body !== "object") return false;
8377
8402
  const envelope = body;
@@ -8405,7 +8430,7 @@ function createStructuralGate(schemaEngine, options = {}) {
8405
8430
  };
8406
8431
  }
8407
8432
 
8408
- // ../src/validation/stateful-gate.ts
8433
+ // ../core/src/validation/stateful-gate.ts
8409
8434
  function createStatefulGate() {
8410
8435
  const rules = /* @__PURE__ */ new Map();
8411
8436
  return {
@@ -8451,7 +8476,7 @@ function createStatefulGate() {
8451
8476
  };
8452
8477
  }
8453
8478
 
8454
- // ../src/validation/capability-gate.ts
8479
+ // ../core/src/validation/capability-gate.ts
8455
8480
  var fail = (reason) => ({ passed: false, gate: "capability", reason });
8456
8481
  var MAX_CLOCK_SKEW_SECONDS = 300;
8457
8482
  function createCapabilityGate(config) {
@@ -8494,7 +8519,7 @@ function createCapabilityGate(config) {
8494
8519
  };
8495
8520
  }
8496
8521
 
8497
- // ../src/validation/validation-engine.ts
8522
+ // ../core/src/validation/validation-engine.ts
8498
8523
  function createValidationEngine(config) {
8499
8524
  const { cryptoGate, structuralGate, statefulGate, capabilityGate, resolvePublicKey, getExpression } = config;
8500
8525
  return {
@@ -8520,7 +8545,7 @@ function createValidationEngine(config) {
8520
8545
  };
8521
8546
  }
8522
8547
 
8523
- // ../src/query/filter.ts
8548
+ // ../core/src/query/filter.ts
8524
8549
  var MAX_INCLUDE_DEPTH = 3;
8525
8550
  var META = {
8526
8551
  "@key": (r) => r.key,
@@ -8684,7 +8709,7 @@ function checkQuery(query) {
8684
8709
  return null;
8685
8710
  }
8686
8711
 
8687
- // ../src/query/types.ts
8712
+ // ../core/src/query/types.ts
8688
8713
  var nameOf = (ref) => typeof ref === "object" && ref !== null && typeof ref.name === "string" ? ref.name : ref;
8689
8714
  function plainQuery(query) {
8690
8715
  const isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
@@ -8698,7 +8723,7 @@ function plainQuery(query) {
8698
8723
  return { ...query, collection: nameOf(query.collection), ...query.include !== void 0 ? { include: includes(query.include) } : {} };
8699
8724
  }
8700
8725
 
8701
- // ../src/query/engine.ts
8726
+ // ../core/src/query/engine.ts
8702
8727
  function compareValues(a, b) {
8703
8728
  if (a === b) return 0;
8704
8729
  if (a === void 0 || a === null) return -1;
@@ -8758,7 +8783,7 @@ async function runQuery(source, given) {
8758
8783
  return { records, cursor: more ? page[page.length - 1].key : null, complete: true };
8759
8784
  }
8760
8785
 
8761
- // ../src/storage/segment.ts
8786
+ // ../core/src/storage/segment.ts
8762
8787
  var SEGMENT_SUFFIX = ".seg";
8763
8788
  function packSegment(versions) {
8764
8789
  return utf8Encode(JSON.stringify({ v: 1, versions }));
@@ -8778,7 +8803,7 @@ async function segmentName(counter, bytes) {
8778
8803
  }
8779
8804
  var versionSize = (version) => utf8Encode(JSON.stringify(version)).length;
8780
8805
 
8781
- // ../src/storage/mirror.ts
8806
+ // ../core/src/storage/mirror.ts
8782
8807
  var WRITER_KEY = "mirror:writer";
8783
8808
  var COUNTER_KEY = "mirror:counter";
8784
8809
  var readKey = (name) => `mirror:read:${name}`;
@@ -8924,7 +8949,7 @@ async function createMirror(config) {
8924
8949
  });
8925
8950
  }
8926
8951
 
8927
- // ../src/space/pass.ts
8952
+ // ../core/src/space/pass.ts
8928
8953
  var PASS_COLLECTION = "sys.pass";
8929
8954
  var CARRY_CLOSED_KEY = "carry:closed";
8930
8955
  async function passKey(spaceId) {
@@ -8955,7 +8980,7 @@ function carriedRecord(pass) {
8955
8980
  return { space: pass.space, key: null, invite: null, role: null, read: pass.read };
8956
8981
  }
8957
8982
 
8958
- // ../src/node/space-runtime.ts
8983
+ // ../core/src/node/space-runtime.ts
8959
8984
  var MANAGED = /* @__PURE__ */ new Set([MEMBERSHIP_COLLECTION, PROFILE_COLLECTION, CARRIER_COLLECTION, PASS_COLLECTION, BOX_COLLECTION, MEMBER_KEY_COLLECTION, ...ACCESS_COLLECTIONS]);
8960
8985
  var IN_THE_CLEAR = /* @__PURE__ */ new Set([ROLE_COLLECTION, MEMBER_COLLECTION, INVITE_COLLECTION, REVOKE_COLLECTION, KEY_COLLECTION, BOX_COLLECTION, MEMBER_KEY_COLLECTION, RELAYS_COLLECTION, KEEPERS_COLLECTION]);
8961
8986
  var ACCESS_KEYS = [
@@ -9462,6 +9487,7 @@ async function openSpaceRuntime(deps) {
9462
9487
  rules: definition?.rules ?? {},
9463
9488
  topics: definition?.topics ?? [],
9464
9489
  ...definition?.screen !== void 0 ? { screen: definition.screen } : {},
9490
+ ...definition?.network?.length ? { network: definition.network } : {},
9465
9491
  records
9466
9492
  });
9467
9493
  }
@@ -9701,6 +9727,12 @@ async function openSpaceRuntime(deps) {
9701
9727
  return held ? deriveReadKey(held, provider) : record.read ?? null;
9702
9728
  },
9703
9729
  current: () => currentReadKey,
9730
+ async earlier() {
9731
+ const { current } = (await access()).history;
9732
+ const newest = newestKey(current);
9733
+ const older = [...current.keys].reverse().map((epoch) => keyring.get(epoch.keyId)).filter((held) => held !== void 0 && held !== newest);
9734
+ return Promise.all(older.slice(0, MAX_EARLIER_READ_KEYS).map((held) => deriveReadKey(held, provider)));
9735
+ },
9704
9736
  async membership() {
9705
9737
  const held = newestKey((await access()).history.current);
9706
9738
  return held ? sealWith(held, session.proof(), membershipContext(space2.id)) : null;
@@ -9898,7 +9930,7 @@ async function openSpaceRuntime(deps) {
9898
9930
  channel.unref?.();
9899
9931
  }
9900
9932
  const HOUR = 36e5;
9901
- const DAY2 = 24 * HOUR;
9933
+ const DAY3 = 24 * HOUR;
9902
9934
  const declared = new Set((cache?.collections ?? []).filter((c) => !c.startsWith("sys.")));
9903
9935
  let cacheTimer = null;
9904
9936
  const saveCache = () => {
@@ -9986,7 +10018,7 @@ async function openSpaceRuntime(deps) {
9986
10018
  });
9987
10019
  async function dropUnused() {
9988
10020
  if (!cache || !partial || closed) return;
9989
- const cutoff = Date.now() - (cache.unusedAfterDays ?? 30) * DAY2;
10021
+ const cutoff = Date.now() - (cache.unusedAfterDays ?? 30) * DAY3;
9990
10022
  const waiting = /* @__PURE__ */ new Set();
9991
10023
  for (const name of await adapter.list(PENDING_PREFIX)) {
9992
10024
  const bytes = await adapter.get(name);
@@ -10266,7 +10298,8 @@ async function openSpaceRuntime(deps) {
10266
10298
  ...input.permissions !== void 0 ? { permissions: input.permissions } : {},
10267
10299
  ...input.rules !== void 0 ? { rules: input.rules } : {},
10268
10300
  ...input.topics !== void 0 ? { topics: input.topics } : {},
10269
- ...input.screen !== void 0 ? { screen: input.screen } : {}
10301
+ ...input.screen !== void 0 ? { screen: input.screen } : {},
10302
+ ...input.network?.length ? { network: input.network } : {}
10270
10303
  };
10271
10304
  const problem = checkStoredCollection(definition);
10272
10305
  if (problem) throw new Error(problem);
@@ -10414,7 +10447,7 @@ async function openSpaceRuntime(deps) {
10414
10447
  });
10415
10448
  }
10416
10449
 
10417
- // ../src/schemas/contacts.ts
10450
+ // ../core/src/schemas/contacts.ts
10418
10451
  var typed = () => (definition) => definition;
10419
10452
  var contact = typed()({
10420
10453
  name: "std.contact",
@@ -10495,7 +10528,7 @@ var knockAnswer = typed()({
10495
10528
  rules: { edit: "creator", delete: "creator" }
10496
10529
  });
10497
10530
 
10498
- // ../src/doors/doors.ts
10531
+ // ../core/src/doors/doors.ts
10499
10532
  var MAX_DOOR_RELAYS = 3;
10500
10533
  var KNOCK_TTL_SECONDS = 14 * 24 * 3600;
10501
10534
  var KNOCK_DROP_WINDOW_SECONDS = 600;
@@ -10622,7 +10655,7 @@ async function openKnock(doorKey, blob, receivedAt, provider) {
10622
10655
  });
10623
10656
  }
10624
10657
 
10625
- // ../src/network/mailbox.ts
10658
+ // ../core/src/network/mailbox.ts
10626
10659
  var MAX_PAGES = 8;
10627
10660
  function createMailboxClient(options = {}) {
10628
10661
  const timeoutMs = options.timeoutMs ?? 8e3;
@@ -10716,7 +10749,7 @@ function isMailItem(value) {
10716
10749
  return !!item && Number.isSafeInteger(item.seq) && typeof item.id === "string" && typeof item.at === "number" && typeof item.blob === "string";
10717
10750
  }
10718
10751
 
10719
- // ../src/space/notify.ts
10752
+ // ../core/src/space/notify.ts
10720
10753
  var NOTIFY_COLLECTION = "sys.notify";
10721
10754
  var SUBSCRIPTION_COLLECTION = "sys.subscription";
10722
10755
  var COLLECTION = /^[a-z][a-z0-9-]*(\.[a-z0-9-]+)+$/;
@@ -10744,8 +10777,21 @@ function checkNotify(when) {
10744
10777
  }
10745
10778
  }
10746
10779
  if (typeof w.since !== "string" || !Number.isFinite(Date.parse(w.since))) return "since must be a date";
10780
+ if (w.app !== void 0) {
10781
+ const a = w.app;
10782
+ if (typeof a !== "object" || a === null || typeof a.origin !== "string" || !isOrigin(a.origin)) return "app.origin must be a web origin";
10783
+ if (a.name !== void 0 && (typeof a.name !== "string" || a.name.length > 80)) return "app.name must be text, at most 80 characters";
10784
+ }
10747
10785
  return null;
10748
10786
  }
10787
+ function isOrigin(value) {
10788
+ if (/^(chrome|moz|safari-web)-extension:\/\/[a-z0-9-]{1,64}$/i.test(value)) return true;
10789
+ try {
10790
+ return new URL(value).origin === value;
10791
+ } catch {
10792
+ return false;
10793
+ }
10794
+ }
10749
10795
  async function carriedFor(when, spaces) {
10750
10796
  let tags;
10751
10797
  if (when.topic) {
@@ -10803,7 +10849,7 @@ function matchesSubscription(sub, spaceId, version, account, now = Date.now()) {
10803
10849
  return true;
10804
10850
  }
10805
10851
 
10806
- // ../src/session/hosting.ts
10852
+ // ../core/src/session/hosting.ts
10807
10853
  var HOSTING_COLLECTION = "sys.hosting";
10808
10854
  var REQUEST_WINDOW_SECONDS = 300;
10809
10855
  function newSubscriptionSeed() {
@@ -10919,7 +10965,7 @@ function createHostClient(url, host, key3, provider = createP256Provider()) {
10919
10965
  });
10920
10966
  }
10921
10967
 
10922
- // ../src/node/node.ts
10968
+ // ../core/src/node/node.ts
10923
10969
  var SESSION_CAPABILITY = { with: "*", can: "expression/*" };
10924
10970
  var DEFAULT_TTL_SECONDS = 3600;
10925
10971
  var FORGET_CARRIER_AFTER_MS = 30 * 24 * 3600 * 1e3;
@@ -12344,7 +12390,7 @@ async function createNode(config) {
12344
12390
  return node;
12345
12391
  }
12346
12392
 
12347
- // ../src/node/stores.ts
12393
+ // ../core/src/node/stores.ts
12348
12394
  function folderStores(directory2, options = {}) {
12349
12395
  return async (path7, storeOptions) => {
12350
12396
  const full = options.basePath ? `${options.basePath}/${path7}` : path7;
@@ -12353,7 +12399,7 @@ function folderStores(directory2, options = {}) {
12353
12399
  };
12354
12400
  }
12355
12401
 
12356
- // ../src/network/local-transport.ts
12402
+ // ../core/src/network/local-transport.ts
12357
12403
  var later = (fn) => void setTimeout(fn, 0);
12358
12404
  function createLocalHub() {
12359
12405
  const connected = /* @__PURE__ */ new Map();
@@ -12399,7 +12445,7 @@ function createLocalHub() {
12399
12445
  };
12400
12446
  }
12401
12447
 
12402
- // ../src/node/carrier.ts
12448
+ // ../core/src/node/carrier.ts
12403
12449
  async function createCarryCore(config) {
12404
12450
  const provider = config.provider ?? createP256Provider();
12405
12451
  const signer = createSigner(provider);
@@ -12604,6 +12650,16 @@ async function createCarryCore(config) {
12604
12650
  await refreshing;
12605
12651
  return [...carries.values()].flatMap((entry) => [...entry.subscriptions].map(([id, sub]) => view(id, sub)));
12606
12652
  },
12653
+ async collections(spaceId) {
12654
+ const entry = carried.get(spaceId);
12655
+ if (!entry || carries.has(spaceId)) return [];
12656
+ return (await entry.runtime.collections()).map((found) => ({
12657
+ name: found.name,
12658
+ ...found.title ? { title: found.title } : {},
12659
+ topics: found.topics,
12660
+ records: found.records
12661
+ }));
12662
+ },
12607
12663
  async spaces() {
12608
12664
  const found = [];
12609
12665
  for (const [spaceId, entry] of carried) {
@@ -12630,7 +12686,7 @@ async function createCarryCore(config) {
12630
12686
  };
12631
12687
  }
12632
12688
 
12633
- // ../src/node/host.ts
12689
+ // ../core/src/node/host.ts
12634
12690
  var SUBSCRIPTION_PREFIX = "subscription:";
12635
12691
  var NotAllowedError = class extends Error {
12636
12692
  constructor() {
@@ -12792,7 +12848,7 @@ async function createHostNode(config) {
12792
12848
  return Object.freeze(host);
12793
12849
  }
12794
12850
 
12795
- // ../src/records/describe.ts
12851
+ // ../core/src/records/describe.ts
12796
12852
  var KNOWN_RULES = ["create", "edit", "delete", "onePer", "fixed"];
12797
12853
  var words = (name) => name.replace(/^.*\./, "").replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").toLowerCase();
12798
12854
  var article = (noun) => /^[aeiou]/.test(noun) ? `an ${noun}` : `a ${noun}`;
@@ -12867,10 +12923,14 @@ function describeCollection(definition) {
12867
12923
  sentences.push(`Roles in the space can be given permission to ${list}.`);
12868
12924
  }
12869
12925
  if (definition.history === "all") sentences.push(`Every earlier version of ${a} is kept.`);
12926
+ if (definition.network?.length) {
12927
+ const hosts = joinAnd(definition.network.map((origin) => origin.replace(/^[a-z]+:\/\//, "")));
12928
+ sentences.push(`Its screen can connect to ${hosts}, and send there anything the person looking can see in it. Each person is asked first.`);
12929
+ }
12870
12930
  return sentences;
12871
12931
  }
12872
12932
 
12873
- // ../src/schemas/apps.ts
12933
+ // ../core/src/schemas/apps.ts
12874
12934
  var MAX_APP_COLLECTIONS = 10;
12875
12935
  var app = {
12876
12936
  name: "std.app",
@@ -12926,7 +12986,8 @@ function essence(definition) {
12926
12986
  links: canonicalize(definition.links ?? {}),
12927
12987
  permissions: canonicalize([...definition.permissions ?? []].sort()),
12928
12988
  rules: canonicalize(definition.rules ?? {}),
12929
- screen: definition.screen ?? ""
12989
+ screen: definition.screen ?? "",
12990
+ network: canonicalize([...definition.network ?? []].sort())
12930
12991
  };
12931
12992
  }
12932
12993
  var fieldNames = (schema) => Object.keys(schema?.properties ?? {});
@@ -12945,12 +13006,17 @@ function differences(held, wanted) {
12945
13006
  if (a.links !== b.links) out.push("changes what it points at");
12946
13007
  if (a.history !== b.history) out.push(b.history === "all" ? "starts keeping every version" : "stops keeping old versions");
12947
13008
  if (a.screen !== b.screen) out.push(!b.screen ? "takes its screen away" : !a.screen ? "gives it a screen" : "changes its screen");
13009
+ if (a.network !== b.network) {
13010
+ const before2 = new Set(held.network ?? []);
13011
+ const reached = (wanted.network ?? []).filter((origin) => !before2.has(origin));
13012
+ out.push(reached.length ? `lets its screen reach ${reached.join(", ")}` : "lets its screen reach fewer places");
13013
+ }
12948
13014
  if (a.title !== b.title || a.description !== b.description) out.push("renames or redescribes it");
12949
13015
  return out;
12950
13016
  }
12951
13017
  function appScreen(body) {
12952
13018
  const found = body.needs.find((need) => typeof need.screen === "string" && need.screen.trim());
12953
- return found ? { collection: found.name, screen: found.screen } : null;
13019
+ return found ? { collection: found.name, screen: found.screen, network: found.network ?? [] } : null;
12954
13020
  }
12955
13021
  function reviewApp(body, collections) {
12956
13022
  const problem = checkApp(body);
@@ -12971,11 +13037,17 @@ async function proposeApp(node, spaceId, body) {
12971
13037
  return node.records.put(spaceId, app.name, body);
12972
13038
  }
12973
13039
 
12974
- // ../src/schemas/screens.ts
13040
+ // ../core/src/schemas/screens.ts
12975
13041
  var SCREEN_GUIDE = `A screen is one HTML document (scripts and styles inline, at most 48 KB) put on a collection definition as "screen".
12976
13042
  When the app is added, apps in the space can show it instead of plain lists. It runs sealed: no network (no fetch, no
12977
13043
  images from URLs, no fonts from URLs), no storage, no popups, no forms that submit. Keep state in records, never in the page.
12978
13044
 
13045
+ If it truly needs the network \u2014 weather, a map, a timetable \u2014 give its collection "network": the exact origins it may
13046
+ fetch from and load images from, like ["https://api.open-meteo.com"] (https:// or wss://, a host, no path; at most 8).
13047
+ Nothing else is reachable, and fonts never are. Each origin is named when people review the app, and each person is asked
13048
+ before a screen they open gets it: a person who says no gets the screen sealed, so it must still work without them.
13049
+ Ask for no more than it needs.
13050
+
12979
13051
  Inside it, window.weave is:
12980
13052
  weave.me { did, name } \u2014 who is looking
12981
13053
  weave.collections the collection names this screen may use (its app's)
@@ -12997,7 +13069,7 @@ Draw everything from weave.list(...) and redraw on weave.onChange \u2014 other p
12997
13069
  Two people can write at once: design collections so that clashes resolve by the rules (onePer, creator-only edits), not by the screen.
12998
13070
  Use plain DOM; no libraries can be loaded. Prefer system fonts and simple, readable styling that works in light and dark.`;
12999
13071
 
13000
- // ../src/node/actions.ts
13072
+ // ../core/src/node/actions.ts
13001
13073
  var space = { type: "string", description: "Space id, from spaces_list" };
13002
13074
  var key2 = { type: "string", description: "Record key \u2014 stays the same when the record is edited" };
13003
13075
  var str = (input, key3) => input[key3];
@@ -13178,7 +13250,12 @@ var NODE_ACTIONS = Object.freeze([
13178
13250
  type: "object",
13179
13251
  description: 'What its records allow, enforced by every peer: { "create": "member", "edit": "creator", "delete": ["creator", "can:moderate"], "onePer": ["@author", "link:about"], "fixed": ["options"] }. Who is "member" (anyone holding a role here, the default), "creator" (whoever created that record) or "can:<permission>" (anyone whose role holds one of the permissions declared above). onePer makes at most one record per author + linked record (+ body field): writing again changes it. fixed fields keep their first value.'
13180
13252
  },
13181
- screen: { type: "string", description: "Optional: its own screen, one HTML document \u2014 read apps_screen_guide first" }
13253
+ screen: { type: "string", description: "Optional: its own screen, one HTML document \u2014 read apps_screen_guide first" },
13254
+ network: {
13255
+ type: "array",
13256
+ items: { type: "string" },
13257
+ description: 'Optional, with a screen: the exact origins it may connect to and load images from, like ["https://api.open-meteo.com"]. None keeps it sealed. Each is named when people review the app, and each person is asked before their screen gets them.'
13258
+ }
13182
13259
  },
13183
13260
  required: ["space", "name", "schema"]
13184
13261
  },
@@ -13195,7 +13272,8 @@ var NODE_ACTIONS = Object.freeze([
13195
13272
  ...typeof input.links === "object" && input.links !== null ? { links: input.links } : {},
13196
13273
  ...Array.isArray(input.permissions) ? { permissions: input.permissions.filter((p) => typeof p === "string") } : {},
13197
13274
  ...typeof input.rules === "object" && input.rules !== null ? { rules: input.rules } : {},
13198
- ...typeof input.screen === "string" ? { screen: input.screen } : {}
13275
+ ...typeof input.screen === "string" ? { screen: input.screen } : {},
13276
+ ...Array.isArray(input.network) ? { network: input.network.filter((o) => typeof o === "string") } : {}
13199
13277
  });
13200
13278
  return { ...defined, summary: describeCollection({ ...defined, schema: defined.schema ?? void 0 }) };
13201
13279
  }
@@ -13228,14 +13306,14 @@ var NODE_ACTIONS = Object.freeze([
13228
13306
  },
13229
13307
  {
13230
13308
  name: "apps_screen_guide",
13231
- description: `How to write a screen \u2014 an app's own HTML UI, kept on its main collection's definition as "screen" and run sealed in the app: what it can use (window.weave: list, put, update, remove, onChange, me) and what it can't (network, storage).`,
13309
+ description: `How to write a screen \u2014 an app's own HTML UI, kept on its main collection's definition as "screen" and run sealed in the app: what it can use (window.weave: list, put, update, remove, onChange, me) and what it can't (storage, and the network beyond the origins it names).`,
13232
13310
  input: { type: "object", properties: {} },
13233
13311
  readOnly: true,
13234
13312
  run: async () => SCREEN_GUIDE
13235
13313
  },
13236
13314
  {
13237
13315
  name: "apps_propose",
13238
- 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). 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.`,
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.`,
13239
13317
  input: {
13240
13318
  type: "object",
13241
13319
  properties: {
@@ -13438,7 +13516,7 @@ async function runAction(node, name, input = {}) {
13438
13516
  return action.run(node, input);
13439
13517
  }
13440
13518
 
13441
- // ../node_modules/aws4fetch/dist/aws4fetch.esm.mjs
13519
+ // ../../node_modules/aws4fetch/dist/aws4fetch.esm.mjs
13442
13520
  var encoder2 = new TextEncoder();
13443
13521
  var HOST_SERVICES = {
13444
13522
  appstream2: "appstream",
@@ -13717,7 +13795,7 @@ function guessServiceRegion(url, headers) {
13717
13795
  return [HOST_SERVICES[service] || service, region || ""];
13718
13796
  }
13719
13797
 
13720
- // ../src/storage/blob/s3.ts
13798
+ // ../core/src/storage/blob/s3.ts
13721
13799
  var ATTEMPTS = 5;
13722
13800
  function createS3BlobStore(config) {
13723
13801
  const client = new AwsClient({
@@ -13893,7 +13971,7 @@ async function createAccount(home, options) {
13893
13971
  throw new Error(`This home already has an account for ${did}`);
13894
13972
  }
13895
13973
  let vault = createVault({ did, label: options.name, wraps: [] });
13896
- if (options.passphrase) vault = withWrap(vault, await wrapSeedWithPassphrase(seed, options.passphrase, "CLI passphrase"));
13974
+ if (options.passphrase) vault = withWrap(vault, await wrapSeedWithPassphrase(seed, options.passphrase, CLI_PASSPHRASE_LABEL));
13897
13975
  const id = newAccountId();
13898
13976
  const account = {
13899
13977
  id,
@@ -13954,7 +14032,7 @@ async function unlock(home, account, secret) {
13954
14032
  import { createServer } from "node:http";
13955
14033
  import { WebSocketServer } from "ws";
13956
14034
 
13957
- // ../server/relay.mjs
14035
+ // ../relay/relay.mjs
13958
14036
  import { createHash, createHmac, createPublicKey, randomBytes as randomBytes4, verify } from "node:crypto";
13959
14037
  var MAX_MESSAGE_BYTES = 64 * 1024;
13960
14038
  var MAX_CONNECTIONS = 5e3;
@@ -15541,7 +15619,11 @@ import { mkdir as mkdir2, readFile as readFile3, rm, writeFile as writeFile2 } f
15541
15619
  import os3 from "node:os";
15542
15620
  import path4 from "node:path";
15543
15621
 
15544
- // ../src/session/connect.ts
15622
+ // ../core/src/session/stay-signed-in.ts
15623
+ var DAY2 = 24 * 60 * 60 * 1e3;
15624
+ var DURATION2 = { never: 0, "1d": DAY2, "7d": 7 * DAY2, "30d": 30 * DAY2 };
15625
+
15626
+ // ../core/src/session/connect.ts
15545
15627
  function grantSigner(grant) {
15546
15628
  let token = null;
15547
15629
  return {
@@ -15557,7 +15639,7 @@ function grantSigner(grant) {
15557
15639
  };
15558
15640
  }
15559
15641
 
15560
- // ../src/session/agent-link.ts
15642
+ // ../core/src/session/agent-link.ts
15561
15643
  var SECRET_BYTES = 16;
15562
15644
  var ROOM_PREFIX2 = utf8Encode("weave-agent-link-room-v1");
15563
15645
  var KEY_INFO = utf8Encode("weave-agent-link-key-v1");
package/package.json CHANGED
@@ -1,17 +1,19 @@
1
1
  {
2
2
  "name": "@weaveprotocol/cli",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
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": {
7
7
  "weave": "dist/weave.mjs"
8
8
  },
9
9
  "scripts": {
10
- "start": "node --import tsx src/main.ts",
11
- "bundle": "esbuild src/main.ts --bundle --platform=node --format=esm --target=node22 --outfile=dist/weave.mjs --external:ws --external:node-datachannel --banner:js='#!/usr/bin/env node'",
10
+ "start": "node --conditions=@weaveprotocol/source --import tsx src/main.ts",
11
+ "test": "node --conditions=@weaveprotocol/source --import tsx --test tests/*.test.ts",
12
+ "typecheck": "tsc --noEmit",
13
+ "bundle": "esbuild src/main.ts --bundle --platform=node --format=esm --target=node22 --conditions=@weaveprotocol/source --outfile=dist/weave.mjs --external:ws --external:node-datachannel --banner:js='#!/usr/bin/env node'",
14
+ "bundle:pay": "esbuild pay/walletconnect.ts --bundle --format=esm --minify --platform=browser --target=es2022 --outfile=pay/dist/walletconnect.js --define:process.env.NODE_ENV='\"production\"'",
12
15
  "build": "bun build.ts",
13
- "prepublishOnly": "npm run bundle && npm run bundle:pay",
14
- "bundle:pay": "esbuild pay/walletconnect.ts --bundle --format=esm --minify --platform=browser --target=es2022 --outfile=pay/dist/walletconnect.js --define:process.env.NODE_ENV='\"production\"'"
16
+ "prepublishOnly": "npm run bundle && npm run bundle:pay"
15
17
  },
16
18
  "dependencies": {
17
19
  "node-datachannel": "^0.33.4",
@@ -24,7 +26,7 @@
24
26
  "repository": {
25
27
  "type": "git",
26
28
  "url": "git+https://github.com/leifriksheim/weave.git",
27
- "directory": "cli"
29
+ "directory": "packages/cli"
28
30
  },
29
31
  "files": [
30
32
  "dist/weave.mjs",
@@ -33,6 +35,9 @@
33
35
  ],
34
36
  "devDependencies": {
35
37
  "@reown/appkit": "^1.8.24",
38
+ "@types/node": "^26.6.3",
39
+ "@weaveprotocol/core": "*",
40
+ "@weaveprotocol/relay": "*",
36
41
  "esbuild": "^0.28.0",
37
42
  "tsx": "^4.19.0"
38
43
  },