@formstr/mcp 0.1.1 → 0.2.0

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/index.js CHANGED
@@ -766,7 +766,7 @@ var require_validation = __commonJS({
766
766
  var require_receiver = __commonJS({
767
767
  "../../node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/receiver.js"(exports2, module2) {
768
768
  "use strict";
769
- var { Writable } = require("stream");
769
+ var { Writable: Writable2 } = require("stream");
770
770
  var PerMessageDeflate2 = require_permessage_deflate();
771
771
  var {
772
772
  BINARY_TYPES,
@@ -784,7 +784,7 @@ var require_receiver = __commonJS({
784
784
  var GET_DATA = 4;
785
785
  var INFLATING = 5;
786
786
  var DEFER_EVENT = 6;
787
- var Receiver2 = class extends Writable {
787
+ var Receiver2 = class extends Writable2 {
788
788
  /**
789
789
  * Creates a Receiver instance.
790
790
  *
@@ -22056,243 +22056,6 @@ async function validateEvent2(event, url, method, body) {
22056
22056
  return true;
22057
22057
  }
22058
22058
 
22059
- // ../../node_modules/.pnpm/@noble+hashes@2.0.1/node_modules/@noble/hashes/pbkdf2.js
22060
- function pbkdf2Init(hash, _password, _salt, _opts) {
22061
- ahash(hash);
22062
- const opts = checkOpts({ dkLen: 32, asyncTick: 10 }, _opts);
22063
- const { c, dkLen, asyncTick } = opts;
22064
- anumber(c, "c");
22065
- anumber(dkLen, "dkLen");
22066
- anumber(asyncTick, "asyncTick");
22067
- if (c < 1)
22068
- throw new Error("iterations (c) must be >= 1");
22069
- const password = kdfInputToBytes(_password, "password");
22070
- const salt = kdfInputToBytes(_salt, "salt");
22071
- const DK = new Uint8Array(dkLen);
22072
- const PRF = hmac.create(hash, password);
22073
- const PRFSalt = PRF._cloneInto().update(salt);
22074
- return { c, dkLen, asyncTick, DK, PRF, PRFSalt };
22075
- }
22076
- function pbkdf2Output(PRF, PRFSalt, DK, prfW, u) {
22077
- PRF.destroy();
22078
- PRFSalt.destroy();
22079
- if (prfW)
22080
- prfW.destroy();
22081
- clean(u);
22082
- return DK;
22083
- }
22084
- function pbkdf2(hash, password, salt, opts) {
22085
- const { c, dkLen, DK, PRF, PRFSalt } = pbkdf2Init(hash, password, salt, opts);
22086
- let prfW;
22087
- const arr = new Uint8Array(4);
22088
- const view = createView(arr);
22089
- const u = new Uint8Array(PRF.outputLen);
22090
- for (let ti = 1, pos = 0; pos < dkLen; ti++, pos += PRF.outputLen) {
22091
- const Ti = DK.subarray(pos, pos + PRF.outputLen);
22092
- view.setInt32(0, ti, false);
22093
- (prfW = PRFSalt._cloneInto(prfW)).update(arr).digestInto(u);
22094
- Ti.set(u.subarray(0, Ti.length));
22095
- for (let ui = 1; ui < c; ui++) {
22096
- PRF._cloneInto(prfW).update(u).digestInto(u);
22097
- for (let i4 = 0; i4 < Ti.length; i4++)
22098
- Ti[i4] ^= u[i4];
22099
- }
22100
- }
22101
- return pbkdf2Output(PRF, PRFSalt, DK, prfW, u);
22102
- }
22103
-
22104
- // ../../node_modules/.pnpm/@noble+hashes@2.0.1/node_modules/@noble/hashes/scrypt.js
22105
- function XorAndSalsa(prev, pi, input, ii, out, oi) {
22106
- let y00 = prev[pi++] ^ input[ii++], y01 = prev[pi++] ^ input[ii++];
22107
- let y02 = prev[pi++] ^ input[ii++], y03 = prev[pi++] ^ input[ii++];
22108
- let y04 = prev[pi++] ^ input[ii++], y05 = prev[pi++] ^ input[ii++];
22109
- let y06 = prev[pi++] ^ input[ii++], y07 = prev[pi++] ^ input[ii++];
22110
- let y08 = prev[pi++] ^ input[ii++], y09 = prev[pi++] ^ input[ii++];
22111
- let y10 = prev[pi++] ^ input[ii++], y11 = prev[pi++] ^ input[ii++];
22112
- let y12 = prev[pi++] ^ input[ii++], y13 = prev[pi++] ^ input[ii++];
22113
- let y14 = prev[pi++] ^ input[ii++], y15 = prev[pi++] ^ input[ii++];
22114
- let x00 = y00, x01 = y01, x02 = y02, x03 = y03, x04 = y04, x05 = y05, x06 = y06, x07 = y07, x08 = y08, x09 = y09, x10 = y10, x11 = y11, x12 = y12, x13 = y13, x14 = y14, x15 = y15;
22115
- for (let i4 = 0; i4 < 8; i4 += 2) {
22116
- x04 ^= rotl(x00 + x12 | 0, 7);
22117
- x08 ^= rotl(x04 + x00 | 0, 9);
22118
- x12 ^= rotl(x08 + x04 | 0, 13);
22119
- x00 ^= rotl(x12 + x08 | 0, 18);
22120
- x09 ^= rotl(x05 + x01 | 0, 7);
22121
- x13 ^= rotl(x09 + x05 | 0, 9);
22122
- x01 ^= rotl(x13 + x09 | 0, 13);
22123
- x05 ^= rotl(x01 + x13 | 0, 18);
22124
- x14 ^= rotl(x10 + x06 | 0, 7);
22125
- x02 ^= rotl(x14 + x10 | 0, 9);
22126
- x06 ^= rotl(x02 + x14 | 0, 13);
22127
- x10 ^= rotl(x06 + x02 | 0, 18);
22128
- x03 ^= rotl(x15 + x11 | 0, 7);
22129
- x07 ^= rotl(x03 + x15 | 0, 9);
22130
- x11 ^= rotl(x07 + x03 | 0, 13);
22131
- x15 ^= rotl(x11 + x07 | 0, 18);
22132
- x01 ^= rotl(x00 + x03 | 0, 7);
22133
- x02 ^= rotl(x01 + x00 | 0, 9);
22134
- x03 ^= rotl(x02 + x01 | 0, 13);
22135
- x00 ^= rotl(x03 + x02 | 0, 18);
22136
- x06 ^= rotl(x05 + x04 | 0, 7);
22137
- x07 ^= rotl(x06 + x05 | 0, 9);
22138
- x04 ^= rotl(x07 + x06 | 0, 13);
22139
- x05 ^= rotl(x04 + x07 | 0, 18);
22140
- x11 ^= rotl(x10 + x09 | 0, 7);
22141
- x08 ^= rotl(x11 + x10 | 0, 9);
22142
- x09 ^= rotl(x08 + x11 | 0, 13);
22143
- x10 ^= rotl(x09 + x08 | 0, 18);
22144
- x12 ^= rotl(x15 + x14 | 0, 7);
22145
- x13 ^= rotl(x12 + x15 | 0, 9);
22146
- x14 ^= rotl(x13 + x12 | 0, 13);
22147
- x15 ^= rotl(x14 + x13 | 0, 18);
22148
- }
22149
- out[oi++] = y00 + x00 | 0;
22150
- out[oi++] = y01 + x01 | 0;
22151
- out[oi++] = y02 + x02 | 0;
22152
- out[oi++] = y03 + x03 | 0;
22153
- out[oi++] = y04 + x04 | 0;
22154
- out[oi++] = y05 + x05 | 0;
22155
- out[oi++] = y06 + x06 | 0;
22156
- out[oi++] = y07 + x07 | 0;
22157
- out[oi++] = y08 + x08 | 0;
22158
- out[oi++] = y09 + x09 | 0;
22159
- out[oi++] = y10 + x10 | 0;
22160
- out[oi++] = y11 + x11 | 0;
22161
- out[oi++] = y12 + x12 | 0;
22162
- out[oi++] = y13 + x13 | 0;
22163
- out[oi++] = y14 + x14 | 0;
22164
- out[oi++] = y15 + x15 | 0;
22165
- }
22166
- function BlockMix(input, ii, out, oi, r) {
22167
- let head = oi + 0;
22168
- let tail = oi + 16 * r;
22169
- for (let i4 = 0; i4 < 16; i4++)
22170
- out[tail + i4] = input[ii + (2 * r - 1) * 16 + i4];
22171
- for (let i4 = 0; i4 < r; i4++, head += 16, ii += 16) {
22172
- XorAndSalsa(out, tail, input, ii, out, head);
22173
- if (i4 > 0)
22174
- tail += 16;
22175
- XorAndSalsa(out, head, input, ii += 16, out, tail);
22176
- }
22177
- }
22178
- function scryptInit(password, salt, _opts) {
22179
- const opts = checkOpts({
22180
- dkLen: 32,
22181
- asyncTick: 10,
22182
- maxmem: 1024 ** 3 + 1024
22183
- }, _opts);
22184
- const { N, r, p, dkLen, asyncTick, maxmem, onProgress } = opts;
22185
- anumber(N, "N");
22186
- anumber(r, "r");
22187
- anumber(p, "p");
22188
- anumber(dkLen, "dkLen");
22189
- anumber(asyncTick, "asyncTick");
22190
- anumber(maxmem, "maxmem");
22191
- if (onProgress !== void 0 && typeof onProgress !== "function")
22192
- throw new Error("progressCb must be a function");
22193
- const blockSize = 128 * r;
22194
- const blockSize32 = blockSize / 4;
22195
- const pow32 = Math.pow(2, 32);
22196
- if (N <= 1 || (N & N - 1) !== 0 || N > pow32)
22197
- throw new Error('"N" expected a power of 2, and 2^1 <= N <= 2^32');
22198
- if (p < 1 || p > (pow32 - 1) * 32 / blockSize)
22199
- throw new Error('"p" expected integer 1..((2^32 - 1) * 32) / (128 * r)');
22200
- if (dkLen < 1 || dkLen > (pow32 - 1) * 32)
22201
- throw new Error('"dkLen" expected integer 1..(2^32 - 1) * 32');
22202
- const memUsed = blockSize * (N + p);
22203
- if (memUsed > maxmem)
22204
- throw new Error('"maxmem" limit was hit, expected 128*r*(N+p) <= "maxmem"=' + maxmem);
22205
- const B = pbkdf2(sha256, password, salt, { c: 1, dkLen: blockSize * p });
22206
- const B32 = u32(B);
22207
- const V = u32(new Uint8Array(blockSize * N));
22208
- const tmp = u32(new Uint8Array(blockSize));
22209
- let blockMixCb = () => {
22210
- };
22211
- if (onProgress) {
22212
- const totalBlockMix = 2 * N * p;
22213
- const callbackPer = Math.max(Math.floor(totalBlockMix / 1e4), 1);
22214
- let blockMixCnt = 0;
22215
- blockMixCb = () => {
22216
- blockMixCnt++;
22217
- if (onProgress && (!(blockMixCnt % callbackPer) || blockMixCnt === totalBlockMix))
22218
- onProgress(blockMixCnt / totalBlockMix);
22219
- };
22220
- }
22221
- return { N, r, p, dkLen, blockSize32, V, B32, B, tmp, blockMixCb, asyncTick };
22222
- }
22223
- function scryptOutput(password, dkLen, B, V, tmp) {
22224
- const res = pbkdf2(sha256, password, B, { c: 1, dkLen });
22225
- clean(B, V, tmp);
22226
- return res;
22227
- }
22228
- function scrypt(password, salt, opts) {
22229
- const { N, r, p, dkLen, blockSize32, V, B32, B, tmp, blockMixCb } = scryptInit(password, salt, opts);
22230
- swap32IfBE(B32);
22231
- for (let pi = 0; pi < p; pi++) {
22232
- const Pi = blockSize32 * pi;
22233
- for (let i4 = 0; i4 < blockSize32; i4++)
22234
- V[i4] = B32[Pi + i4];
22235
- for (let i4 = 0, pos = 0; i4 < N - 1; i4++) {
22236
- BlockMix(V, pos, V, pos += blockSize32, r);
22237
- blockMixCb();
22238
- }
22239
- BlockMix(V, (N - 1) * blockSize32, B32, Pi, r);
22240
- blockMixCb();
22241
- for (let i4 = 0; i4 < N; i4++) {
22242
- const j = (B32[Pi + blockSize32 - 16] & N - 1) >>> 0;
22243
- for (let k = 0; k < blockSize32; k++)
22244
- tmp[k] = B32[Pi + k] ^ V[j * blockSize32 + k];
22245
- BlockMix(tmp, 0, B32, Pi, r);
22246
- blockMixCb();
22247
- }
22248
- }
22249
- swap32IfBE(B32);
22250
- return scryptOutput(password, dkLen, B, V, tmp);
22251
- }
22252
-
22253
- // ../../node_modules/.pnpm/nostr-tools@2.23.3_typescript@5.9.3/node_modules/nostr-tools/lib/esm/nip49.js
22254
- var Bech32MaxSize2 = 5e3;
22255
- function encodeBech322(prefix, data) {
22256
- let words = bech32.toWords(data);
22257
- return bech32.encode(prefix, words, Bech32MaxSize2);
22258
- }
22259
- function encodeBytes2(prefix, bytes) {
22260
- return encodeBech322(prefix, bytes);
22261
- }
22262
- function encrypt3(sec, password, logn = 16, ksb = 2) {
22263
- let salt = randomBytes(16);
22264
- let n = 2 ** logn;
22265
- let key = scrypt(password.normalize("NFKC"), salt, { N: n, r: 8, p: 1, dkLen: 32 });
22266
- let nonce = randomBytes(24);
22267
- let aad = Uint8Array.from([ksb]);
22268
- let xc2p1 = xchacha20poly1305(key, nonce, aad);
22269
- let ciphertext = xc2p1.encrypt(sec);
22270
- let b = concatBytes(Uint8Array.from([2]), Uint8Array.from([logn]), salt, nonce, aad, ciphertext);
22271
- return encodeBytes2("ncryptsec", b);
22272
- }
22273
- function decrypt3(ncryptsec, password) {
22274
- let { prefix, words } = bech32.decode(ncryptsec, Bech32MaxSize2);
22275
- if (prefix !== "ncryptsec") {
22276
- throw new Error(`invalid prefix ${prefix}, expected 'ncryptsec'`);
22277
- }
22278
- let b = new Uint8Array(bech32.fromWords(words));
22279
- let version2 = b[0];
22280
- if (version2 !== 2) {
22281
- throw new Error(`invalid version ${version2}, expected 0x02`);
22282
- }
22283
- let logn = b[1];
22284
- let n = 2 ** logn;
22285
- let salt = b.slice(2, 2 + 16);
22286
- let nonce = b.slice(2 + 16, 2 + 16 + 24);
22287
- let ksb = b[2 + 16 + 24];
22288
- let aad = Uint8Array.from([ksb]);
22289
- let ciphertext = b.slice(2 + 16 + 24 + 1);
22290
- let key = scrypt(password.normalize("NFKC"), salt, { N: n, r: 8, p: 1, dkLen: 32 });
22291
- let xc2p1 = xchacha20poly1305(key, nonce, aad);
22292
- let sec = xc2p1.decrypt(ciphertext);
22293
- return sec;
22294
- }
22295
-
22296
22059
  // ../../node_modules/.pnpm/nostr-tools@2.23.3_typescript@5.9.3/node_modules/nostr-tools/lib/esm/nip46.js
22297
22060
  var verifiedSymbol2 = /* @__PURE__ */ Symbol("verified");
22298
22061
  var isRecord2 = (obj) => obj instanceof Object;
@@ -22468,14 +22231,14 @@ function decodePayload2(payload) {
22468
22231
  mac: data.subarray(-32)
22469
22232
  };
22470
22233
  }
22471
- function encrypt4(plaintext, conversationKey, nonce = randomBytes(32)) {
22234
+ function encrypt3(plaintext, conversationKey, nonce = randomBytes(32)) {
22472
22235
  const { chacha_key, chacha_nonce, hmac_key } = getMessageKeys2(conversationKey, nonce);
22473
22236
  const padded = pad2(plaintext);
22474
22237
  const ciphertext = chacha20(chacha_key, chacha_nonce, padded);
22475
22238
  const mac = hmacAad2(hmac_key, ciphertext, nonce);
22476
22239
  return base64.encode(concatBytes(new Uint8Array([2]), nonce, ciphertext, mac));
22477
22240
  }
22478
- function decrypt4(payload, conversationKey) {
22241
+ function decrypt3(payload, conversationKey) {
22479
22242
  const { nonce, ciphertext, mac } = decodePayload2(payload);
22480
22243
  const { chacha_key, chacha_nonce, hmac_key } = getMessageKeys2(conversationKey, nonce);
22481
22244
  const calculatedMac = hmacAad2(hmac_key, ciphertext, nonce);
@@ -23426,7 +23189,7 @@ var BunkerSigner = class {
23426
23189
  onevent: async (event) => {
23427
23190
  try {
23428
23191
  const tempConvKey = getConversationKey2(clientSecretKey, event.pubkey);
23429
- const decryptedContent = decrypt4(event.content, tempConvKey);
23192
+ const decryptedContent = decrypt3(event.content, tempConvKey);
23430
23193
  const response = JSON.parse(decryptedContent);
23431
23194
  if (response.result === uri.searchParams.get("secret")) {
23432
23195
  sub.close();
@@ -23469,7 +23232,7 @@ var BunkerSigner = class {
23469
23232
  },
23470
23233
  {
23471
23234
  onevent: async (event) => {
23472
- const o = JSON.parse(decrypt4(event.content, convKey));
23235
+ const o = JSON.parse(decrypt3(event.content, convKey));
23473
23236
  const { id, result, error: error2 } = o;
23474
23237
  if (result === "auth_url" && waitingForAuth[id]) {
23475
23238
  delete waitingForAuth[id];
@@ -23531,7 +23294,7 @@ var BunkerSigner = class {
23531
23294
  this.setupSubscription();
23532
23295
  this.serial++;
23533
23296
  const id = `${this.idPrefix}-${this.serial}`;
23534
- const encryptedContent = encrypt4(JSON.stringify({ id, method, params }), this.conversationKey);
23297
+ const encryptedContent = encrypt3(JSON.stringify({ id, method, params }), this.conversationKey);
23535
23298
  const verifiedEvent = finalizeEvent2(
23536
23299
  {
23537
23300
  kind: NostrConnect2,
@@ -23586,7 +23349,244 @@ var BunkerSigner = class {
23586
23349
  }
23587
23350
  };
23588
23351
 
23589
- // ../../node_modules/.pnpm/@formstr+signer@0.1.0_typescript@5.9.3/node_modules/@formstr/signer/dist/index.js
23352
+ // ../../node_modules/.pnpm/@noble+hashes@2.0.1/node_modules/@noble/hashes/pbkdf2.js
23353
+ function pbkdf2Init(hash, _password, _salt, _opts) {
23354
+ ahash(hash);
23355
+ const opts = checkOpts({ dkLen: 32, asyncTick: 10 }, _opts);
23356
+ const { c, dkLen, asyncTick } = opts;
23357
+ anumber(c, "c");
23358
+ anumber(dkLen, "dkLen");
23359
+ anumber(asyncTick, "asyncTick");
23360
+ if (c < 1)
23361
+ throw new Error("iterations (c) must be >= 1");
23362
+ const password = kdfInputToBytes(_password, "password");
23363
+ const salt = kdfInputToBytes(_salt, "salt");
23364
+ const DK = new Uint8Array(dkLen);
23365
+ const PRF = hmac.create(hash, password);
23366
+ const PRFSalt = PRF._cloneInto().update(salt);
23367
+ return { c, dkLen, asyncTick, DK, PRF, PRFSalt };
23368
+ }
23369
+ function pbkdf2Output(PRF, PRFSalt, DK, prfW, u) {
23370
+ PRF.destroy();
23371
+ PRFSalt.destroy();
23372
+ if (prfW)
23373
+ prfW.destroy();
23374
+ clean(u);
23375
+ return DK;
23376
+ }
23377
+ function pbkdf2(hash, password, salt, opts) {
23378
+ const { c, dkLen, DK, PRF, PRFSalt } = pbkdf2Init(hash, password, salt, opts);
23379
+ let prfW;
23380
+ const arr = new Uint8Array(4);
23381
+ const view = createView(arr);
23382
+ const u = new Uint8Array(PRF.outputLen);
23383
+ for (let ti = 1, pos = 0; pos < dkLen; ti++, pos += PRF.outputLen) {
23384
+ const Ti = DK.subarray(pos, pos + PRF.outputLen);
23385
+ view.setInt32(0, ti, false);
23386
+ (prfW = PRFSalt._cloneInto(prfW)).update(arr).digestInto(u);
23387
+ Ti.set(u.subarray(0, Ti.length));
23388
+ for (let ui = 1; ui < c; ui++) {
23389
+ PRF._cloneInto(prfW).update(u).digestInto(u);
23390
+ for (let i4 = 0; i4 < Ti.length; i4++)
23391
+ Ti[i4] ^= u[i4];
23392
+ }
23393
+ }
23394
+ return pbkdf2Output(PRF, PRFSalt, DK, prfW, u);
23395
+ }
23396
+
23397
+ // ../../node_modules/.pnpm/@noble+hashes@2.0.1/node_modules/@noble/hashes/scrypt.js
23398
+ function XorAndSalsa(prev, pi, input, ii, out, oi) {
23399
+ let y00 = prev[pi++] ^ input[ii++], y01 = prev[pi++] ^ input[ii++];
23400
+ let y02 = prev[pi++] ^ input[ii++], y03 = prev[pi++] ^ input[ii++];
23401
+ let y04 = prev[pi++] ^ input[ii++], y05 = prev[pi++] ^ input[ii++];
23402
+ let y06 = prev[pi++] ^ input[ii++], y07 = prev[pi++] ^ input[ii++];
23403
+ let y08 = prev[pi++] ^ input[ii++], y09 = prev[pi++] ^ input[ii++];
23404
+ let y10 = prev[pi++] ^ input[ii++], y11 = prev[pi++] ^ input[ii++];
23405
+ let y12 = prev[pi++] ^ input[ii++], y13 = prev[pi++] ^ input[ii++];
23406
+ let y14 = prev[pi++] ^ input[ii++], y15 = prev[pi++] ^ input[ii++];
23407
+ let x00 = y00, x01 = y01, x02 = y02, x03 = y03, x04 = y04, x05 = y05, x06 = y06, x07 = y07, x08 = y08, x09 = y09, x10 = y10, x11 = y11, x12 = y12, x13 = y13, x14 = y14, x15 = y15;
23408
+ for (let i4 = 0; i4 < 8; i4 += 2) {
23409
+ x04 ^= rotl(x00 + x12 | 0, 7);
23410
+ x08 ^= rotl(x04 + x00 | 0, 9);
23411
+ x12 ^= rotl(x08 + x04 | 0, 13);
23412
+ x00 ^= rotl(x12 + x08 | 0, 18);
23413
+ x09 ^= rotl(x05 + x01 | 0, 7);
23414
+ x13 ^= rotl(x09 + x05 | 0, 9);
23415
+ x01 ^= rotl(x13 + x09 | 0, 13);
23416
+ x05 ^= rotl(x01 + x13 | 0, 18);
23417
+ x14 ^= rotl(x10 + x06 | 0, 7);
23418
+ x02 ^= rotl(x14 + x10 | 0, 9);
23419
+ x06 ^= rotl(x02 + x14 | 0, 13);
23420
+ x10 ^= rotl(x06 + x02 | 0, 18);
23421
+ x03 ^= rotl(x15 + x11 | 0, 7);
23422
+ x07 ^= rotl(x03 + x15 | 0, 9);
23423
+ x11 ^= rotl(x07 + x03 | 0, 13);
23424
+ x15 ^= rotl(x11 + x07 | 0, 18);
23425
+ x01 ^= rotl(x00 + x03 | 0, 7);
23426
+ x02 ^= rotl(x01 + x00 | 0, 9);
23427
+ x03 ^= rotl(x02 + x01 | 0, 13);
23428
+ x00 ^= rotl(x03 + x02 | 0, 18);
23429
+ x06 ^= rotl(x05 + x04 | 0, 7);
23430
+ x07 ^= rotl(x06 + x05 | 0, 9);
23431
+ x04 ^= rotl(x07 + x06 | 0, 13);
23432
+ x05 ^= rotl(x04 + x07 | 0, 18);
23433
+ x11 ^= rotl(x10 + x09 | 0, 7);
23434
+ x08 ^= rotl(x11 + x10 | 0, 9);
23435
+ x09 ^= rotl(x08 + x11 | 0, 13);
23436
+ x10 ^= rotl(x09 + x08 | 0, 18);
23437
+ x12 ^= rotl(x15 + x14 | 0, 7);
23438
+ x13 ^= rotl(x12 + x15 | 0, 9);
23439
+ x14 ^= rotl(x13 + x12 | 0, 13);
23440
+ x15 ^= rotl(x14 + x13 | 0, 18);
23441
+ }
23442
+ out[oi++] = y00 + x00 | 0;
23443
+ out[oi++] = y01 + x01 | 0;
23444
+ out[oi++] = y02 + x02 | 0;
23445
+ out[oi++] = y03 + x03 | 0;
23446
+ out[oi++] = y04 + x04 | 0;
23447
+ out[oi++] = y05 + x05 | 0;
23448
+ out[oi++] = y06 + x06 | 0;
23449
+ out[oi++] = y07 + x07 | 0;
23450
+ out[oi++] = y08 + x08 | 0;
23451
+ out[oi++] = y09 + x09 | 0;
23452
+ out[oi++] = y10 + x10 | 0;
23453
+ out[oi++] = y11 + x11 | 0;
23454
+ out[oi++] = y12 + x12 | 0;
23455
+ out[oi++] = y13 + x13 | 0;
23456
+ out[oi++] = y14 + x14 | 0;
23457
+ out[oi++] = y15 + x15 | 0;
23458
+ }
23459
+ function BlockMix(input, ii, out, oi, r) {
23460
+ let head = oi + 0;
23461
+ let tail = oi + 16 * r;
23462
+ for (let i4 = 0; i4 < 16; i4++)
23463
+ out[tail + i4] = input[ii + (2 * r - 1) * 16 + i4];
23464
+ for (let i4 = 0; i4 < r; i4++, head += 16, ii += 16) {
23465
+ XorAndSalsa(out, tail, input, ii, out, head);
23466
+ if (i4 > 0)
23467
+ tail += 16;
23468
+ XorAndSalsa(out, head, input, ii += 16, out, tail);
23469
+ }
23470
+ }
23471
+ function scryptInit(password, salt, _opts) {
23472
+ const opts = checkOpts({
23473
+ dkLen: 32,
23474
+ asyncTick: 10,
23475
+ maxmem: 1024 ** 3 + 1024
23476
+ }, _opts);
23477
+ const { N, r, p, dkLen, asyncTick, maxmem, onProgress } = opts;
23478
+ anumber(N, "N");
23479
+ anumber(r, "r");
23480
+ anumber(p, "p");
23481
+ anumber(dkLen, "dkLen");
23482
+ anumber(asyncTick, "asyncTick");
23483
+ anumber(maxmem, "maxmem");
23484
+ if (onProgress !== void 0 && typeof onProgress !== "function")
23485
+ throw new Error("progressCb must be a function");
23486
+ const blockSize = 128 * r;
23487
+ const blockSize32 = blockSize / 4;
23488
+ const pow32 = Math.pow(2, 32);
23489
+ if (N <= 1 || (N & N - 1) !== 0 || N > pow32)
23490
+ throw new Error('"N" expected a power of 2, and 2^1 <= N <= 2^32');
23491
+ if (p < 1 || p > (pow32 - 1) * 32 / blockSize)
23492
+ throw new Error('"p" expected integer 1..((2^32 - 1) * 32) / (128 * r)');
23493
+ if (dkLen < 1 || dkLen > (pow32 - 1) * 32)
23494
+ throw new Error('"dkLen" expected integer 1..(2^32 - 1) * 32');
23495
+ const memUsed = blockSize * (N + p);
23496
+ if (memUsed > maxmem)
23497
+ throw new Error('"maxmem" limit was hit, expected 128*r*(N+p) <= "maxmem"=' + maxmem);
23498
+ const B = pbkdf2(sha256, password, salt, { c: 1, dkLen: blockSize * p });
23499
+ const B32 = u32(B);
23500
+ const V = u32(new Uint8Array(blockSize * N));
23501
+ const tmp = u32(new Uint8Array(blockSize));
23502
+ let blockMixCb = () => {
23503
+ };
23504
+ if (onProgress) {
23505
+ const totalBlockMix = 2 * N * p;
23506
+ const callbackPer = Math.max(Math.floor(totalBlockMix / 1e4), 1);
23507
+ let blockMixCnt = 0;
23508
+ blockMixCb = () => {
23509
+ blockMixCnt++;
23510
+ if (onProgress && (!(blockMixCnt % callbackPer) || blockMixCnt === totalBlockMix))
23511
+ onProgress(blockMixCnt / totalBlockMix);
23512
+ };
23513
+ }
23514
+ return { N, r, p, dkLen, blockSize32, V, B32, B, tmp, blockMixCb, asyncTick };
23515
+ }
23516
+ function scryptOutput(password, dkLen, B, V, tmp) {
23517
+ const res = pbkdf2(sha256, password, B, { c: 1, dkLen });
23518
+ clean(B, V, tmp);
23519
+ return res;
23520
+ }
23521
+ function scrypt(password, salt, opts) {
23522
+ const { N, r, p, dkLen, blockSize32, V, B32, B, tmp, blockMixCb } = scryptInit(password, salt, opts);
23523
+ swap32IfBE(B32);
23524
+ for (let pi = 0; pi < p; pi++) {
23525
+ const Pi = blockSize32 * pi;
23526
+ for (let i4 = 0; i4 < blockSize32; i4++)
23527
+ V[i4] = B32[Pi + i4];
23528
+ for (let i4 = 0, pos = 0; i4 < N - 1; i4++) {
23529
+ BlockMix(V, pos, V, pos += blockSize32, r);
23530
+ blockMixCb();
23531
+ }
23532
+ BlockMix(V, (N - 1) * blockSize32, B32, Pi, r);
23533
+ blockMixCb();
23534
+ for (let i4 = 0; i4 < N; i4++) {
23535
+ const j = (B32[Pi + blockSize32 - 16] & N - 1) >>> 0;
23536
+ for (let k = 0; k < blockSize32; k++)
23537
+ tmp[k] = B32[Pi + k] ^ V[j * blockSize32 + k];
23538
+ BlockMix(tmp, 0, B32, Pi, r);
23539
+ blockMixCb();
23540
+ }
23541
+ }
23542
+ swap32IfBE(B32);
23543
+ return scryptOutput(password, dkLen, B, V, tmp);
23544
+ }
23545
+
23546
+ // ../../node_modules/.pnpm/nostr-tools@2.23.3_typescript@5.9.3/node_modules/nostr-tools/lib/esm/nip49.js
23547
+ var Bech32MaxSize2 = 5e3;
23548
+ function encodeBech322(prefix, data) {
23549
+ let words = bech32.toWords(data);
23550
+ return bech32.encode(prefix, words, Bech32MaxSize2);
23551
+ }
23552
+ function encodeBytes2(prefix, bytes) {
23553
+ return encodeBech322(prefix, bytes);
23554
+ }
23555
+ function encrypt4(sec, password, logn = 16, ksb = 2) {
23556
+ let salt = randomBytes(16);
23557
+ let n = 2 ** logn;
23558
+ let key = scrypt(password.normalize("NFKC"), salt, { N: n, r: 8, p: 1, dkLen: 32 });
23559
+ let nonce = randomBytes(24);
23560
+ let aad = Uint8Array.from([ksb]);
23561
+ let xc2p1 = xchacha20poly1305(key, nonce, aad);
23562
+ let ciphertext = xc2p1.encrypt(sec);
23563
+ let b = concatBytes(Uint8Array.from([2]), Uint8Array.from([logn]), salt, nonce, aad, ciphertext);
23564
+ return encodeBytes2("ncryptsec", b);
23565
+ }
23566
+ function decrypt4(ncryptsec, password) {
23567
+ let { prefix, words } = bech32.decode(ncryptsec, Bech32MaxSize2);
23568
+ if (prefix !== "ncryptsec") {
23569
+ throw new Error(`invalid prefix ${prefix}, expected 'ncryptsec'`);
23570
+ }
23571
+ let b = new Uint8Array(bech32.fromWords(words));
23572
+ let version2 = b[0];
23573
+ if (version2 !== 2) {
23574
+ throw new Error(`invalid version ${version2}, expected 0x02`);
23575
+ }
23576
+ let logn = b[1];
23577
+ let n = 2 ** logn;
23578
+ let salt = b.slice(2, 2 + 16);
23579
+ let nonce = b.slice(2 + 16, 2 + 16 + 24);
23580
+ let ksb = b[2 + 16 + 24];
23581
+ let aad = Uint8Array.from([ksb]);
23582
+ let ciphertext = b.slice(2 + 16 + 24 + 1);
23583
+ let key = scrypt(password.normalize("NFKC"), salt, { N: n, r: 8, p: 1, dkLen: 32 });
23584
+ let xc2p1 = xchacha20poly1305(key, nonce, aad);
23585
+ let sec = xc2p1.decrypt(ciphertext);
23586
+ return sec;
23587
+ }
23588
+
23589
+ // ../../node_modules/.pnpm/@formstr+signer@0.2.2_typescript@5.9.3/node_modules/@formstr/signer/dist/index.js
23590
23590
  var DEFAULT_PREFIX = "@formstr/signer:";
23591
23591
  function localStorageAdapter(prefix = DEFAULT_PREFIX) {
23592
23592
  const ls = () => {
@@ -23645,16 +23645,16 @@ var LocalSigner = class {
23645
23645
  }
23646
23646
  };
23647
23647
  function encryptSecretKey(secretKey, passphrase) {
23648
- return encrypt3(secretKey, passphrase);
23648
+ return encrypt4(secretKey, passphrase);
23649
23649
  }
23650
23650
  function decryptNcryptsec(ncryptsec, passphrase) {
23651
- return decrypt3(ncryptsec, passphrase);
23651
+ return decrypt4(ncryptsec, passphrase);
23652
23652
  }
23653
23653
  function generateAccount(passphrase) {
23654
23654
  const secretKey = generateSecretKey();
23655
23655
  const pubkey = getPublicKey(secretKey);
23656
23656
  const npub2 = nip19_exports.npubEncode(pubkey);
23657
- const ncryptsec = encrypt3(secretKey, passphrase);
23657
+ const ncryptsec = encrypt4(secretKey, passphrase);
23658
23658
  return { secretKey, pubkey, npub: npub2, ncryptsec };
23659
23659
  }
23660
23660
  function getWindowNostr() {
@@ -23696,10 +23696,15 @@ var ExtensionSigner = class {
23696
23696
  };
23697
23697
  var BunkerSigner2 = class {
23698
23698
  #delegate;
23699
- constructor(delegate) {
23699
+ #cachedUserPubkey;
23700
+ constructor(delegate, cachedUserPubkey) {
23700
23701
  this.#delegate = delegate;
23702
+ this.#cachedUserPubkey = cachedUserPubkey ?? null;
23701
23703
  }
23702
23704
  getPublicKey() {
23705
+ if (this.#cachedUserPubkey !== null) {
23706
+ return Promise.resolve(this.#cachedUserPubkey);
23707
+ }
23703
23708
  return this.#delegate.getPublicKey();
23704
23709
  }
23705
23710
  signEvent(event) {
@@ -23775,7 +23780,7 @@ async function connectWithBunkerUri(uri, options = {}) {
23775
23780
  options.onRelayMismatch
23776
23781
  );
23777
23782
  return {
23778
- signer: new BunkerSigner2(tools),
23783
+ signer: new BunkerSigner2(tools, pubkey),
23779
23784
  pubkey,
23780
23785
  pointer: { ...pointer, relays: resolvedRelays },
23781
23786
  clientSecretKey
@@ -23811,7 +23816,7 @@ function initiateNostrConnect(options) {
23811
23816
  options.onRelayMismatch
23812
23817
  );
23813
23818
  return {
23814
- signer: new BunkerSigner2(tools),
23819
+ signer: new BunkerSigner2(tools, pubkey),
23815
23820
  pubkey,
23816
23821
  pointer: { ...tools.bp, relays: resolvedRelays },
23817
23822
  clientSecretKey
@@ -23899,28 +23904,57 @@ var AndroidSigner = class {
23899
23904
  return result;
23900
23905
  }
23901
23906
  };
23907
+ function describeIdentifier(value) {
23908
+ if (value === null) return "null";
23909
+ if (value === void 0) return "undefined";
23910
+ if (typeof value !== "string") {
23911
+ return `<${typeof value}>`;
23912
+ }
23913
+ if (value.length === 0) return "empty string";
23914
+ const prefix = value.slice(0, 12);
23915
+ const suffix = value.length > 12 ? "\u2026" : "";
23916
+ return `"${prefix}${suffix}" (length=${value.length})`;
23917
+ }
23918
+ var HEX_PUBKEY_RE = /^[0-9a-f]{64}$/i;
23902
23919
  async function loginWithAndroidSigner(plugin, packageName) {
23903
23920
  if (packageName) {
23904
23921
  await plugin.setPackageName(packageName);
23905
23922
  }
23906
- const { npub: npub2, package: pluginPackage } = await plugin.getPublicKey(packageName);
23923
+ const { npub: rawIdentifier, package: pluginPackage } = await plugin.getPublicKey(packageName);
23907
23924
  const resolvedPackage = pluginPackage || packageName;
23908
23925
  if (!resolvedPackage) {
23909
23926
  throw new Error(
23910
23927
  "@formstr/signer: android signer did not return a package name and none was supplied"
23911
23928
  );
23912
23929
  }
23913
- const decoded = nip19_exports.decode(npub2);
23914
- if (decoded.type !== "npub") {
23915
- throw new Error("@formstr/signer: android signer returned a non-npub identifier");
23916
- }
23930
+ const { pubkey, npub: npub2 } = normalizeAndroidIdentifier(rawIdentifier);
23917
23931
  return {
23918
- signer: new AndroidSigner(plugin, resolvedPackage, npub2, decoded.data),
23919
- pubkey: decoded.data,
23932
+ signer: new AndroidSigner(plugin, resolvedPackage, npub2, pubkey),
23933
+ pubkey,
23920
23934
  npub: npub2,
23921
23935
  packageName: resolvedPackage
23922
23936
  };
23923
23937
  }
23938
+ function normalizeAndroidIdentifier(rawIdentifier) {
23939
+ if (typeof rawIdentifier === "string" && HEX_PUBKEY_RE.test(rawIdentifier)) {
23940
+ const pubkey = rawIdentifier.toLowerCase();
23941
+ return { pubkey, npub: nip19_exports.npubEncode(pubkey) };
23942
+ }
23943
+ let decoded;
23944
+ try {
23945
+ decoded = nip19_exports.decode(rawIdentifier);
23946
+ } catch (e) {
23947
+ throw new Error(
23948
+ `@formstr/signer: android signer returned an undecodable identifier (got ${describeIdentifier(rawIdentifier)}): ${e.message}`
23949
+ );
23950
+ }
23951
+ if (decoded.type !== "npub") {
23952
+ throw new Error(
23953
+ `@formstr/signer: android signer returned a non-npub identifier (type=${decoded.type}, got ${describeIdentifier(rawIdentifier)})`
23954
+ );
23955
+ }
23956
+ return { pubkey: decoded.data, npub: rawIdentifier };
23957
+ }
23924
23958
  var ACCOUNTS_KEY = "accounts";
23925
23959
  var ACTIVE_KEY = "active-pubkey";
23926
23960
  var Signer = class {
@@ -24183,6 +24217,88 @@ var Signer = class {
24183
24217
  getActiveSigner() {
24184
24218
  return this.#activeSigner;
24185
24219
  }
24220
+ /**
24221
+ * Silently unlock the active account from persisted state — no user
24222
+ * prompt, no fresh pairing. The package already keeps everything it
24223
+ * needs to reconstruct the runtime signer on disk; this method is the
24224
+ * way to actually use that on cold start instead of re-running each
24225
+ * method's first-time login flow.
24226
+ *
24227
+ * Behavior by method:
24228
+ *
24229
+ * - `extension`: constructs an {@link ExtensionSigner}, which just
24230
+ * proxies to `window.nostr`. No setup roundtrip — individual
24231
+ * operations may still prompt depending on the extension's own
24232
+ * permission state, but unlock itself does not.
24233
+ *
24234
+ * - `nip46`: reuses the stored `clientSecretKey` to construct a
24235
+ * {@link BunkerSigner} against the stored bunker pubkey + relays
24236
+ * via `BunkerSigner.fromBunker`. Deliberately skips the `connect`
24237
+ * request — the remote signer (Amber etc.) approved this client
24238
+ * pubkey on first pairing and re-sending `connect` is what surfaces
24239
+ * a fresh approval prompt every cold start. Requires `options.pool`
24240
+ * so the BunkerSigner has somewhere to listen for responses.
24241
+ * The cached user pubkey is fed into the wrapper so a follow-up
24242
+ * `getPublicKey()` is a memory read, not a relay request.
24243
+ *
24244
+ * - `android`: constructs an {@link AndroidSigner} directly from the
24245
+ * stored `androidPackageName` + `pubkey` + `npub`. Skips the
24246
+ * `getPublicKey` content-provider roundtrip that
24247
+ * {@link loginWithAndroidSigner} performs and that — on Amber —
24248
+ * surfaces as a permission prompt every cold start.
24249
+ *
24250
+ * - `ncryptsec`: returns `null`. There is no silent path — the user's
24251
+ * passphrase isn't (and shouldn't be) persisted. The caller must
24252
+ * drive the passphrase prompt and call {@link loginWithNcryptsec}.
24253
+ *
24254
+ * Returns `null` (without emitting any event or mutating state) when
24255
+ * there is no active account, when the account is missing fields
24256
+ * required to unlock, when `nip46` is the method but no `pool` was
24257
+ * supplied, or when `android` is the method but no plugin is
24258
+ * configured. On success emits the same `login` / `switch` event the
24259
+ * corresponding `loginWith*` would.
24260
+ */
24261
+ async unlock(options = {}) {
24262
+ const account = this.getActiveAccount();
24263
+ if (!account) return null;
24264
+ switch (account.method) {
24265
+ case "extension": {
24266
+ const signer = new ExtensionSigner();
24267
+ this.#setActive(account, signer);
24268
+ return signer;
24269
+ }
24270
+ case "nip46": {
24271
+ if (!account.nip46) return null;
24272
+ if (!options.pool) return null;
24273
+ const { remoteSignerPubkey, relays, clientSecretKey } = account.nip46;
24274
+ if (!remoteSignerPubkey || !relays.length || !clientSecretKey) {
24275
+ return null;
24276
+ }
24277
+ const tools = BunkerSigner.fromBunker(
24278
+ hexToBytes2(clientSecretKey),
24279
+ { pubkey: remoteSignerPubkey, relays, secret: null },
24280
+ { pool: options.pool }
24281
+ );
24282
+ const signer = new BunkerSigner2(tools, account.pubkey);
24283
+ this.#setActive(account, signer);
24284
+ return signer;
24285
+ }
24286
+ case "android": {
24287
+ if (!account.androidPackageName) return null;
24288
+ if (!this.#defaultAndroidPlugin) return null;
24289
+ const signer = new AndroidSigner(
24290
+ this.#defaultAndroidPlugin,
24291
+ account.androidPackageName,
24292
+ account.npub,
24293
+ account.pubkey
24294
+ );
24295
+ this.#setActive(account, signer);
24296
+ return signer;
24297
+ }
24298
+ case "ncryptsec":
24299
+ return null;
24300
+ }
24301
+ }
24186
24302
  /**
24187
24303
  * Make `pubkey` the active account. Clears the in-memory signer —
24188
24304
  * the new account starts **locked** even if it was previously
@@ -25400,25 +25516,39 @@ function createPatchedPool() {
25400
25516
 
25401
25517
  // src/auth/terminal.ts
25402
25518
  var readline = __toESM(require("readline"));
25519
+ var import_node_stream = require("stream");
25403
25520
  var import_qrcode = __toESM(require_lib());
25404
25521
  function createTerminalIo() {
25405
- const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
25522
+ const stderr = process.stderr;
25523
+ let muted = false;
25524
+ const output = new import_node_stream.Writable({
25525
+ write(chunk, _enc, cb) {
25526
+ if (!muted) stderr.write(chunk);
25527
+ cb();
25528
+ }
25529
+ });
25530
+ Object.defineProperties(output, {
25531
+ isTTY: { get: () => stderr.isTTY },
25532
+ columns: { get: () => stderr.columns },
25533
+ rows: { get: () => stderr.rows }
25534
+ });
25535
+ const rl = readline.createInterface({
25536
+ input: process.stdin,
25537
+ output,
25538
+ terminal: Boolean(process.stdin.isTTY)
25539
+ });
25406
25540
  return {
25407
25541
  prompt(question) {
25408
25542
  return new Promise((resolve) => rl.question(question, resolve));
25409
25543
  },
25410
25544
  promptPassphrase(question) {
25411
25545
  return new Promise((resolve) => {
25412
- process.stderr.write(question);
25413
- const muted = rl;
25414
- const original = muted._writeToOutput?.bind(rl);
25415
- muted._writeToOutput = () => {
25416
- };
25417
- rl.question("", (answer) => {
25418
- muted._writeToOutput = original;
25419
- process.stderr.write("\n");
25546
+ rl.question(question, (answer) => {
25547
+ muted = false;
25548
+ stderr.write("\n");
25420
25549
  resolve(answer);
25421
25550
  });
25551
+ muted = true;
25422
25552
  });
25423
25553
  },
25424
25554
  close() {
@@ -26027,15 +26157,29 @@ var EventStore = class {
26027
26157
  eventsByDTag = /* @__PURE__ */ new Map();
26028
26158
  // "kind:pubkey:dtag" → event
26029
26159
  deletedIds = /* @__PURE__ */ new Set();
26160
+ // NIP-09 coordinate tombstones: "kind:pubkey:dtag" → newest deletion created_at.
26161
+ // An addressable event is only rejected when its created_at ≤ that time, so a
26162
+ // legitimate re-publish after a delete survives.
26163
+ deletedCoordinates = /* @__PURE__ */ new Map();
26164
+ // Kind-84 participant-removal tombstones.
26165
+ ignoredEventIds = /* @__PURE__ */ new Set();
26166
+ ignoredCoordinates = /* @__PURE__ */ new Set();
26030
26167
  subscriptions = [];
26031
26168
  /** Store event. Returns false if duplicate or older replaceable. */
26032
26169
  store(event) {
26033
26170
  if (this.deletedIds.has(event.id))
26034
26171
  return false;
26172
+ if (this.ignoredEventIds.has(event.id))
26173
+ return false;
26035
26174
  if (this.eventsById.has(event.id))
26036
26175
  return false;
26037
26176
  if (this.isReplaceable(event.kind) || this.isParameterizedReplaceable(event.kind)) {
26038
26177
  const addr = this.getAddress(event);
26178
+ const deletedAt = this.deletedCoordinates.get(addr);
26179
+ if (deletedAt !== void 0 && event.created_at <= deletedAt)
26180
+ return false;
26181
+ if (this.ignoredCoordinates.has(addr))
26182
+ return false;
26039
26183
  const existing = this.eventsByDTag.get(addr);
26040
26184
  if (existing && existing.created_at >= event.created_at) {
26041
26185
  return false;
@@ -26050,6 +26194,9 @@ var EventStore = class {
26050
26194
  if (event.kind === 5) {
26051
26195
  this.handleDeletion(event);
26052
26196
  }
26197
+ if (event.kind === 84) {
26198
+ this.handleParticipantRemoval(event);
26199
+ }
26053
26200
  this.eventsById.set(event.id, event);
26054
26201
  if (!this.eventsByKind.has(event.kind)) {
26055
26202
  this.eventsByKind.set(event.kind, /* @__PURE__ */ new Map());
@@ -26118,14 +26265,50 @@ var EventStore = class {
26118
26265
  if (tag[0] === "e" && tag[1]) {
26119
26266
  const targetId = tag[1];
26120
26267
  const target = this.eventsById.get(targetId);
26121
- if (target && target.pubkey === deletionEvent.pubkey) {
26122
- this.remove(targetId);
26123
- }
26268
+ if (target && target.pubkey !== deletionEvent.pubkey)
26269
+ continue;
26124
26270
  this.deletedIds.add(targetId);
26271
+ if (target)
26272
+ this.remove(targetId);
26125
26273
  }
26126
26274
  if (tag[0] === "a" && tag[1]) {
26275
+ const coordAuthor = tag[1].split(":")[1];
26276
+ if (coordAuthor && coordAuthor !== deletionEvent.pubkey)
26277
+ continue;
26278
+ const prev = this.deletedCoordinates.get(tag[1]) ?? 0;
26279
+ if (deletionEvent.created_at > prev) {
26280
+ this.deletedCoordinates.set(tag[1], deletionEvent.created_at);
26281
+ }
26127
26282
  const existing = this.eventsByDTag.get(tag[1]);
26128
- if (existing && existing.pubkey === deletionEvent.pubkey) {
26283
+ if (existing && existing.created_at <= deletionEvent.created_at) {
26284
+ this.remove(existing.id);
26285
+ this.eventsByDTag.delete(tag[1]);
26286
+ }
26287
+ }
26288
+ }
26289
+ }
26290
+ /**
26291
+ * Process a kind-84 participant removal (a participant opting out of an
26292
+ * event). Unlike NIP-09 there is no same-author rule — any participant
26293
+ * (`p` tag on the target) may remove it for themselves; targets that aren't
26294
+ * cached yet are tombstoned unconditionally, matching the standalone
26295
+ * calendar's EventStore.
26296
+ */
26297
+ handleParticipantRemoval(removalEvent) {
26298
+ const isParticipant = (target) => target.tags.some((t) => t[0] === "p" && t[1] === removalEvent.pubkey);
26299
+ for (const tag of removalEvent.tags) {
26300
+ if (tag[0] === "e" && tag[1]) {
26301
+ const existing = this.eventsById.get(tag[1]);
26302
+ if (existing) {
26303
+ if (!isParticipant(existing))
26304
+ continue;
26305
+ this.remove(tag[1]);
26306
+ }
26307
+ this.ignoredEventIds.add(tag[1]);
26308
+ } else if (tag[0] === "a" && tag[1]) {
26309
+ this.ignoredCoordinates.add(tag[1]);
26310
+ const existing = this.eventsByDTag.get(tag[1]);
26311
+ if (existing && isParticipant(existing)) {
26129
26312
  this.remove(existing.id);
26130
26313
  this.eventsByDTag.delete(tag[1]);
26131
26314
  }
@@ -26149,6 +26332,9 @@ var EventStore = class {
26149
26332
  this.eventsByAuthor.clear();
26150
26333
  this.eventsByDTag.clear();
26151
26334
  this.deletedIds.clear();
26335
+ this.deletedCoordinates.clear();
26336
+ this.ignoredEventIds.clear();
26337
+ this.ignoredCoordinates.clear();
26152
26338
  this.subscriptions = [];
26153
26339
  }
26154
26340
  get size() {
@@ -26385,9 +26571,20 @@ var NostrRuntime = class {
26385
26571
  }
26386
26572
  });
26387
26573
  }
26388
- /** Publish event to relays */
26389
- async publish(relays, event) {
26390
- await Promise.allSettled(this.pool.publish(relays, event));
26574
+ /**
26575
+ * Publish event to relays. Bounded by `timeoutMs`: a relay that never acks must
26576
+ * not hang the caller (e.g. a form submit) forever — best-effort fan-out.
26577
+ */
26578
+ async publish(relays, event, timeoutMs = 1e4) {
26579
+ let timer;
26580
+ const timeout = new Promise((resolve) => {
26581
+ timer = setTimeout(resolve, timeoutMs);
26582
+ });
26583
+ try {
26584
+ await Promise.race([Promise.allSettled(this.pool.publish(relays, event)), timeout]);
26585
+ } finally {
26586
+ clearTimeout(timer);
26587
+ }
26391
26588
  this.eventStore.store(event);
26392
26589
  }
26393
26590
  /** Close all subscriptions + clear caches. Use in tests; rarely in production. */
@@ -26451,7 +26648,7 @@ var MODULE_DEFAULT_RELAYS = {
26451
26648
  "wss://relay.primal.net",
26452
26649
  "wss://nos.lol",
26453
26650
  "wss://relay.nostr.wirednet.jp",
26454
- "wss://relay.yakinonne.com",
26651
+ "wss://nostr-01.yakihonne.com",
26455
26652
  "wss://relay.snort.social",
26456
26653
  "wss://relay.nostr.band",
26457
26654
  "wss://nostr21.com"
@@ -26683,7 +26880,7 @@ var BlossomClient = class {
26683
26880
  */
26684
26881
  async upload(data, authEvent, contentType) {
26685
26882
  const authHeader = `Nostr ${btoa(JSON.stringify(authEvent))}`;
26686
- const sha2562 = await sha256Hex(data);
26883
+ const sha2564 = await sha256Hex(data);
26687
26884
  const response = await fetch(`${this.serverUrl}/upload`, {
26688
26885
  method: "PUT",
26689
26886
  headers: {
@@ -26691,7 +26888,7 @@ var BlossomClient = class {
26691
26888
  "Content-Type": contentType ?? "application/octet-stream",
26692
26889
  // BUD-02: lets servers verify the blob and is required by some (e.g.
26693
26890
  // the standalone formstr-drive's default servers).
26694
- "X-SHA-256": sha2562
26891
+ "X-SHA-256": sha2564
26695
26892
  },
26696
26893
  body: data
26697
26894
  });
@@ -26701,26 +26898,26 @@ var BlossomClient = class {
26701
26898
  const text = await response.text();
26702
26899
  try {
26703
26900
  const json = JSON.parse(text);
26704
- const hash = json.sha256 ?? json.x ?? sha2562;
26901
+ const hash = json.sha256 ?? json.x ?? sha2564;
26705
26902
  return {
26706
26903
  sha256: hash,
26707
26904
  url: json.url ?? `${this.serverUrl}/${hash}`,
26708
26905
  size: json.size ?? data.byteLength
26709
26906
  };
26710
26907
  } catch {
26711
- const hash = text.trim() || sha2562;
26908
+ const hash = text.trim() || sha2564;
26712
26909
  return { sha256: hash, url: `${this.serverUrl}/${hash}`, size: data.byteLength };
26713
26910
  }
26714
26911
  }
26715
26912
  /**
26716
26913
  * BUD-03: Download file blob by SHA-256 hash.
26717
26914
  */
26718
- async download(sha2562, authEvent) {
26915
+ async download(sha2564, authEvent) {
26719
26916
  const headers = {};
26720
26917
  if (authEvent) {
26721
26918
  headers["Authorization"] = `Nostr ${btoa(JSON.stringify(authEvent))}`;
26722
26919
  }
26723
- const response = await fetch(`${this.serverUrl}/${sha2562}`, { headers });
26920
+ const response = await fetch(`${this.serverUrl}/${sha2564}`, { headers });
26724
26921
  if (!response.ok) {
26725
26922
  throw new Error(`Blossom download failed: ${response.status} ${response.statusText}`);
26726
26923
  }
@@ -26730,9 +26927,9 @@ var BlossomClient = class {
26730
26927
  /**
26731
26928
  * BUD-04: Delete file blob by SHA-256 hash.
26732
26929
  */
26733
- async delete(sha2562, authEvent) {
26930
+ async delete(sha2564, authEvent) {
26734
26931
  const authHeader = `Nostr ${btoa(JSON.stringify(authEvent))}`;
26735
- const response = await fetch(`${this.serverUrl}/${sha2562}`, {
26932
+ const response = await fetch(`${this.serverUrl}/${sha2564}`, {
26736
26933
  method: "DELETE",
26737
26934
  headers: { Authorization: authHeader }
26738
26935
  });
@@ -26751,18 +26948,18 @@ async function sha256Hex(data) {
26751
26948
  }
26752
26949
 
26753
26950
  // ../core/dist/blossom/auth.js
26754
- async function createBlossomAuthEvent(operation, sha2562, signer) {
26951
+ async function createBlossomAuthEvent(operation, sha2564, signer) {
26755
26952
  const now2 = Math.floor(Date.now() / 1e3);
26756
26953
  const event = {
26757
26954
  kind: 24242,
26758
26955
  created_at: now2,
26759
26956
  tags: [
26760
26957
  ["t", operation],
26761
- ["x", sha2562],
26958
+ ["x", sha2564],
26762
26959
  ["expiration", String(now2 + 300)]
26763
26960
  // 5 min validity
26764
26961
  ],
26765
- content: `Authorize ${operation} for ${sha2562}`
26962
+ content: `Authorize ${operation} for ${sha2564}`
26766
26963
  };
26767
26964
  return signer.signEvent(event);
26768
26965
  }
@@ -26837,17 +27034,6 @@ async function wrapEvent3(event, signer, recipientPubkey, wrapKind = 1059) {
26837
27034
  const seal = await createSeal2(rumor, signer, recipientPubkey);
26838
27035
  return createWrap2(seal, recipientPubkey, wrapKind);
26839
27036
  }
26840
- async function wrapManyEvents3(event, signer, recipientPubkeys, wrapKind = 1059) {
26841
- const rumor = createRumor2(event);
26842
- rumor.pubkey = await signer.getPublicKey();
26843
- const wraps = [];
26844
- for (const pubkey of recipientPubkeys) {
26845
- const seal = await createSeal2(rumor, signer, pubkey);
26846
- const wrap = await createWrap2(seal, pubkey, wrapKind);
26847
- wraps.push(wrap);
26848
- }
26849
- return wraps;
26850
- }
26851
27037
  async function unwrapEvent3(wrappedEvent, signer) {
26852
27038
  if (!signer.nip44Decrypt) {
26853
27039
  throw new Error("Signer does not support NIP-44 for unwrap");
@@ -26865,21 +27051,34 @@ function randomizeTimestamp(timestamp) {
26865
27051
 
26866
27052
  // ../core/dist/crypto/nkeys.js
26867
27053
  var PREFIX = "nkeys";
27054
+ var BECH32_LIMIT = 2048;
26868
27055
  function encodeNKeys(keys) {
26869
- const tlvBytes = [];
27056
+ const encoder = new TextEncoder();
27057
+ const names = [];
27058
+ const values = [];
26870
27059
  for (const [name, value] of Object.entries(keys)) {
26871
- const nameBytes = new TextEncoder().encode(name);
26872
- tlvBytes.push(0);
26873
- tlvBytes.push(nameBytes.length >> 8, nameBytes.length & 255);
26874
- tlvBytes.push(...nameBytes);
26875
- const valueBytes = new TextEncoder().encode(value);
26876
- tlvBytes.push(1);
26877
- tlvBytes.push(valueBytes.length >> 8, valueBytes.length & 255);
26878
- tlvBytes.push(...valueBytes);
26879
- }
26880
- const data = new Uint8Array(tlvBytes);
26881
- const words = bech32ToWords(data);
26882
- return bech32Encode(PREFIX, words);
27060
+ const nameBytes = encoder.encode(name);
27061
+ const valueBytes = encoder.encode(value);
27062
+ if (nameBytes.length > 255)
27063
+ throw new Error(`nkeys: key name too long (${name.length})`);
27064
+ if (valueBytes.length > 255)
27065
+ throw new Error(`nkeys: value too long for key "${name}"`);
27066
+ names.push(nameBytes);
27067
+ values.push(valueBytes);
27068
+ }
27069
+ const tlvBytes = [];
27070
+ for (const nameBytes of names) {
27071
+ tlvBytes.push(0, nameBytes.length, ...nameBytes);
27072
+ }
27073
+ for (const valueBytes of values) {
27074
+ tlvBytes.push(1, valueBytes.length, ...valueBytes);
27075
+ }
27076
+ const words = bech32ToWords(new Uint8Array(tlvBytes));
27077
+ const encoded = bech32Encode(PREFIX, words);
27078
+ if (encoded.length > BECH32_LIMIT) {
27079
+ throw new Error(`nkeys: encoded length ${encoded.length} exceeds ${BECH32_LIMIT}`);
27080
+ }
27081
+ return encoded;
26883
27082
  }
26884
27083
  var CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
26885
27084
  function bech32Encode(prefix, words) {
@@ -26991,11 +27190,16 @@ async function aesGcmDecrypt(ciphertext, conversationKey) {
26991
27190
  }
26992
27191
  async function encryptFileWithKey(fileBytes) {
26993
27192
  const secretKey = generateSecretKey();
27193
+ const privateKeyHex = bytesToHex(secretKey);
27194
+ const ciphertext = await encryptFileWithExistingKey(fileBytes, privateKeyHex);
27195
+ return { ciphertext, privateKeyHex };
27196
+ }
27197
+ async function encryptFileWithExistingKey(fileBytes, privateKeyHex) {
27198
+ const secretKey = hexToBytes(privateKeyHex);
26994
27199
  const pubkey = getPublicKey(secretKey);
26995
27200
  const conversationKey = nip44_exports.v2.utils.getConversationKey(secretKey, pubkey);
26996
27201
  const plaintextBase64 = uint8ArrayToBase64(fileBytes);
26997
- const ciphertext = await aesGcmEncrypt(plaintextBase64, conversationKey);
26998
- return { ciphertext, privateKeyHex: bytesToHex(secretKey) };
27202
+ return aesGcmEncrypt(plaintextBase64, conversationKey);
26999
27203
  }
27000
27204
  async function decryptFileWithKey(ciphertext, privateKeyHex) {
27001
27205
  const secretKey = hexToBytes(privateKeyHex);
@@ -27021,23 +27225,23 @@ var KIND_MODULE_MAP = {
27021
27225
  30168: "forms",
27022
27226
  // Form template
27023
27227
  // Calendar
27024
- 31922: "calendar",
27025
- // Date-based calendar event
27026
27228
  31923: "calendar",
27027
- // Time-based calendar event
27028
- 31924: "calendar",
27229
+ // Public time-based event (NIP-52)
27230
+ 32678: "calendar",
27231
+ // Private event
27232
+ 32679: "calendar",
27233
+ // Private event (legacy recurring variant, read-only)
27234
+ 32123: "calendar",
27029
27235
  // Calendar list
27030
27236
  // Pages
27031
- 30023: "pages",
27032
- // Long-form content (NIP-23)
27033
- 30024: "pages",
27034
- // Draft article
27237
+ 33457: "pages",
27238
+ // Encrypted markdown doc (nostr-docs)
27035
27239
  // Drive
27036
- 30563: "drive",
27037
- // Drive file tree
27240
+ 34578: "drive",
27241
+ // File metadata (formstr-drive)
27038
27242
  // Polls
27039
27243
  1068: "polls"
27040
- // Poll event
27244
+ // Poll event (NIP-88)
27041
27245
  };
27042
27246
  function createRef(_module, kind, pubkey, identifier, relays = []) {
27043
27247
  return nip19_exports.naddrEncode({ kind, pubkey, identifier, relays });
@@ -27184,10 +27388,10 @@ async function unlock(signer, account, deps) {
27184
27388
  "Stored ncryptsec account is missing its encrypted key. Run `formstr-mcp login`."
27185
27389
  );
27186
27390
  }
27187
- const passphrase = deps.passphrase ?? process.env.FORMSTR_MCP_NCRYPTSEC_PASSPHRASE;
27391
+ const passphrase = deps.passphrase ?? process.env.FORMSTR_MCP_NCRYPTSEC_PASSPHRASE ?? (deps.promptPassphrase ? await deps.promptPassphrase("Passphrase to unlock your key: ") : void 0);
27188
27392
  if (!passphrase) {
27189
27393
  throw new Error(
27190
- "Set FORMSTR_MCP_NCRYPTSEC_PASSPHRASE to unlock the ncryptsec account at boot."
27394
+ "Set FORMSTR_MCP_NCRYPTSEC_PASSPHRASE to unlock the ncryptsec account at boot (or run in an interactive terminal to be prompted)."
27191
27395
  );
27192
27396
  }
27193
27397
  await signer.loginWithNcryptsec(account.ncryptsec, passphrase);
@@ -27198,10 +27402,12 @@ async function unlock(signer, account, deps) {
27198
27402
  throw new Error("Stored nip46 account is missing its session. Run `formstr-mcp login`.");
27199
27403
  }
27200
27404
  const pool = deps.pool ?? createPatchedPool();
27201
- await signer.loginWithBunkerUri(account.nip46.uri, {
27202
- clientSecretKey: hexToBytes2(account.nip46.clientSecretKey),
27203
- pool
27204
- });
27405
+ const active = await signer.unlock({ pool });
27406
+ if (!active) {
27407
+ throw new Error(
27408
+ "Could not resume the stored nip46 session (its keys are incomplete). Run `formstr-mcp login` to re-pair."
27409
+ );
27410
+ }
27205
27411
  return;
27206
27412
  }
27207
27413
  throw new Error(
@@ -31315,6 +31521,7 @@ __export(service_exports, {
31315
31521
  createForm: () => createForm,
31316
31522
  deleteForm: () => deleteForm,
31317
31523
  fetchForm: () => fetchForm,
31524
+ fetchFormKeys: () => fetchFormKeys,
31318
31525
  fetchFormSummaryFromRef: () => fetchFormSummaryFromRef,
31319
31526
  fetchMyForms: () => fetchMyForms,
31320
31527
  fetchResponses: () => fetchResponses,
@@ -31326,34 +31533,328 @@ __export(service_exports, {
31326
31533
  updateForm: () => updateForm
31327
31534
  });
31328
31535
 
31329
- // ../../node_modules/.pnpm/nostr-tools@2.23.3_typescript@5.9.3/node_modules/nostr-tools/lib/esm/utils.js
31330
- var utf8Decoder7 = new TextDecoder("utf-8");
31331
- var utf8Encoder7 = new TextEncoder();
31332
-
31333
- // ../agent/src/services/forms/keys.ts
31334
- function makeViewKeySigner(viewKeyHex) {
31335
- return new LocalSigner2(hexToBytes3(viewKeyHex));
31536
+ // ../../node_modules/.pnpm/@noble+hashes@1.8.0/node_modules/@noble/hashes/esm/utils.js
31537
+ function isBytes4(a) {
31538
+ return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
31336
31539
  }
31337
- function makeSigningKeySigner(signingKeyHex) {
31338
- return new LocalSigner2(hexToBytes3(signingKeyHex));
31540
+ function abytes4(b, ...lengths) {
31541
+ if (!isBytes4(b))
31542
+ throw new Error("Uint8Array expected");
31543
+ if (lengths.length > 0 && !lengths.includes(b.length))
31544
+ throw new Error("Uint8Array expected of length " + lengths + ", got length=" + b.length);
31339
31545
  }
31340
- function encodeFormKeys(signingKeyHex, viewKeyHex) {
31341
- return viewKeyHex ? `${signingKeyHex}:${viewKeyHex}` : signingKeyHex;
31546
+ function aexists3(instance, checkFinished = true) {
31547
+ if (instance.destroyed)
31548
+ throw new Error("Hash instance has been destroyed");
31549
+ if (checkFinished && instance.finished)
31550
+ throw new Error("Hash#digest() has already been called");
31342
31551
  }
31343
- function decodeFormKeys(segment) {
31344
- const idx = segment.indexOf(":");
31345
- if (idx < 0) return { signingKey: segment };
31346
- return { signingKey: segment.slice(0, idx), viewKey: segment.slice(idx + 1) };
31552
+ function aoutput3(out, instance) {
31553
+ abytes4(out);
31554
+ const min = instance.outputLen;
31555
+ if (out.length < min) {
31556
+ throw new Error("digestInto() expects output buffer of length at least " + min);
31557
+ }
31347
31558
  }
31348
- function hexToBytes3(hex) {
31349
- if (hex.length % 2 !== 0) throw new Error("invalid hex length");
31350
- if (hex.length > 0 && !/^[0-9a-fA-F]+$/.test(hex)) throw new Error("invalid hex characters");
31351
- const out = new Uint8Array(hex.length / 2);
31352
- for (let i4 = 0; i4 < out.length; i4++) {
31353
- out[i4] = parseInt(hex.slice(i4 * 2, i4 * 2 + 2), 16);
31559
+ function clean3(...arrays) {
31560
+ for (let i4 = 0; i4 < arrays.length; i4++) {
31561
+ arrays[i4].fill(0);
31354
31562
  }
31355
- return out;
31356
31563
  }
31564
+ function createView3(arr) {
31565
+ return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
31566
+ }
31567
+ function rotr2(word, shift) {
31568
+ return word << 32 - shift | word >>> shift;
31569
+ }
31570
+ function utf8ToBytes2(str) {
31571
+ if (typeof str !== "string")
31572
+ throw new Error("string expected");
31573
+ return new Uint8Array(new TextEncoder().encode(str));
31574
+ }
31575
+ function toBytes(data) {
31576
+ if (typeof data === "string")
31577
+ data = utf8ToBytes2(data);
31578
+ abytes4(data);
31579
+ return data;
31580
+ }
31581
+ var Hash = class {
31582
+ };
31583
+ function createHasher2(hashCons) {
31584
+ const hashC = (msg) => hashCons().update(toBytes(msg)).digest();
31585
+ const tmp = hashCons();
31586
+ hashC.outputLen = tmp.outputLen;
31587
+ hashC.blockLen = tmp.blockLen;
31588
+ hashC.create = () => hashCons();
31589
+ return hashC;
31590
+ }
31591
+
31592
+ // ../../node_modules/.pnpm/@noble+hashes@1.8.0/node_modules/@noble/hashes/esm/_md.js
31593
+ function setBigUint64(view, byteOffset, value, isLE3) {
31594
+ if (typeof view.setBigUint64 === "function")
31595
+ return view.setBigUint64(byteOffset, value, isLE3);
31596
+ const _32n = BigInt(32);
31597
+ const _u32_max = BigInt(4294967295);
31598
+ const wh = Number(value >> _32n & _u32_max);
31599
+ const wl = Number(value & _u32_max);
31600
+ const h = isLE3 ? 4 : 0;
31601
+ const l = isLE3 ? 0 : 4;
31602
+ view.setUint32(byteOffset + h, wh, isLE3);
31603
+ view.setUint32(byteOffset + l, wl, isLE3);
31604
+ }
31605
+ function Chi2(a, b, c) {
31606
+ return a & b ^ ~a & c;
31607
+ }
31608
+ function Maj2(a, b, c) {
31609
+ return a & b ^ a & c ^ b & c;
31610
+ }
31611
+ var HashMD2 = class extends Hash {
31612
+ constructor(blockLen, outputLen, padOffset, isLE3) {
31613
+ super();
31614
+ this.finished = false;
31615
+ this.length = 0;
31616
+ this.pos = 0;
31617
+ this.destroyed = false;
31618
+ this.blockLen = blockLen;
31619
+ this.outputLen = outputLen;
31620
+ this.padOffset = padOffset;
31621
+ this.isLE = isLE3;
31622
+ this.buffer = new Uint8Array(blockLen);
31623
+ this.view = createView3(this.buffer);
31624
+ }
31625
+ update(data) {
31626
+ aexists3(this);
31627
+ data = toBytes(data);
31628
+ abytes4(data);
31629
+ const { view, buffer, blockLen } = this;
31630
+ const len = data.length;
31631
+ for (let pos = 0; pos < len; ) {
31632
+ const take = Math.min(blockLen - this.pos, len - pos);
31633
+ if (take === blockLen) {
31634
+ const dataView = createView3(data);
31635
+ for (; blockLen <= len - pos; pos += blockLen)
31636
+ this.process(dataView, pos);
31637
+ continue;
31638
+ }
31639
+ buffer.set(data.subarray(pos, pos + take), this.pos);
31640
+ this.pos += take;
31641
+ pos += take;
31642
+ if (this.pos === blockLen) {
31643
+ this.process(view, 0);
31644
+ this.pos = 0;
31645
+ }
31646
+ }
31647
+ this.length += data.length;
31648
+ this.roundClean();
31649
+ return this;
31650
+ }
31651
+ digestInto(out) {
31652
+ aexists3(this);
31653
+ aoutput3(out, this);
31654
+ this.finished = true;
31655
+ const { buffer, view, blockLen, isLE: isLE3 } = this;
31656
+ let { pos } = this;
31657
+ buffer[pos++] = 128;
31658
+ clean3(this.buffer.subarray(pos));
31659
+ if (this.padOffset > blockLen - pos) {
31660
+ this.process(view, 0);
31661
+ pos = 0;
31662
+ }
31663
+ for (let i4 = pos; i4 < blockLen; i4++)
31664
+ buffer[i4] = 0;
31665
+ setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE3);
31666
+ this.process(view, 0);
31667
+ const oview = createView3(out);
31668
+ const len = this.outputLen;
31669
+ if (len % 4)
31670
+ throw new Error("_sha2: outputLen should be aligned to 32bit");
31671
+ const outLen = len / 4;
31672
+ const state = this.get();
31673
+ if (outLen > state.length)
31674
+ throw new Error("_sha2: outputLen bigger than state");
31675
+ for (let i4 = 0; i4 < outLen; i4++)
31676
+ oview.setUint32(4 * i4, state[i4], isLE3);
31677
+ }
31678
+ digest() {
31679
+ const { buffer, outputLen } = this;
31680
+ this.digestInto(buffer);
31681
+ const res = buffer.slice(0, outputLen);
31682
+ this.destroy();
31683
+ return res;
31684
+ }
31685
+ _cloneInto(to) {
31686
+ to || (to = new this.constructor());
31687
+ to.set(...this.get());
31688
+ const { blockLen, buffer, length, finished, destroyed, pos } = this;
31689
+ to.destroyed = destroyed;
31690
+ to.finished = finished;
31691
+ to.length = length;
31692
+ to.pos = pos;
31693
+ if (length % blockLen)
31694
+ to.buffer.set(buffer);
31695
+ return to;
31696
+ }
31697
+ clone() {
31698
+ return this._cloneInto();
31699
+ }
31700
+ };
31701
+ var SHA256_IV2 = /* @__PURE__ */ Uint32Array.from([
31702
+ 1779033703,
31703
+ 3144134277,
31704
+ 1013904242,
31705
+ 2773480762,
31706
+ 1359893119,
31707
+ 2600822924,
31708
+ 528734635,
31709
+ 1541459225
31710
+ ]);
31711
+
31712
+ // ../../node_modules/.pnpm/@noble+hashes@1.8.0/node_modules/@noble/hashes/esm/sha2.js
31713
+ var SHA256_K2 = /* @__PURE__ */ Uint32Array.from([
31714
+ 1116352408,
31715
+ 1899447441,
31716
+ 3049323471,
31717
+ 3921009573,
31718
+ 961987163,
31719
+ 1508970993,
31720
+ 2453635748,
31721
+ 2870763221,
31722
+ 3624381080,
31723
+ 310598401,
31724
+ 607225278,
31725
+ 1426881987,
31726
+ 1925078388,
31727
+ 2162078206,
31728
+ 2614888103,
31729
+ 3248222580,
31730
+ 3835390401,
31731
+ 4022224774,
31732
+ 264347078,
31733
+ 604807628,
31734
+ 770255983,
31735
+ 1249150122,
31736
+ 1555081692,
31737
+ 1996064986,
31738
+ 2554220882,
31739
+ 2821834349,
31740
+ 2952996808,
31741
+ 3210313671,
31742
+ 3336571891,
31743
+ 3584528711,
31744
+ 113926993,
31745
+ 338241895,
31746
+ 666307205,
31747
+ 773529912,
31748
+ 1294757372,
31749
+ 1396182291,
31750
+ 1695183700,
31751
+ 1986661051,
31752
+ 2177026350,
31753
+ 2456956037,
31754
+ 2730485921,
31755
+ 2820302411,
31756
+ 3259730800,
31757
+ 3345764771,
31758
+ 3516065817,
31759
+ 3600352804,
31760
+ 4094571909,
31761
+ 275423344,
31762
+ 430227734,
31763
+ 506948616,
31764
+ 659060556,
31765
+ 883997877,
31766
+ 958139571,
31767
+ 1322822218,
31768
+ 1537002063,
31769
+ 1747873779,
31770
+ 1955562222,
31771
+ 2024104815,
31772
+ 2227730452,
31773
+ 2361852424,
31774
+ 2428436474,
31775
+ 2756734187,
31776
+ 3204031479,
31777
+ 3329325298
31778
+ ]);
31779
+ var SHA256_W2 = /* @__PURE__ */ new Uint32Array(64);
31780
+ var SHA256 = class extends HashMD2 {
31781
+ constructor(outputLen = 32) {
31782
+ super(64, outputLen, 8, false);
31783
+ this.A = SHA256_IV2[0] | 0;
31784
+ this.B = SHA256_IV2[1] | 0;
31785
+ this.C = SHA256_IV2[2] | 0;
31786
+ this.D = SHA256_IV2[3] | 0;
31787
+ this.E = SHA256_IV2[4] | 0;
31788
+ this.F = SHA256_IV2[5] | 0;
31789
+ this.G = SHA256_IV2[6] | 0;
31790
+ this.H = SHA256_IV2[7] | 0;
31791
+ }
31792
+ get() {
31793
+ const { A, B, C, D, E, F, G, H } = this;
31794
+ return [A, B, C, D, E, F, G, H];
31795
+ }
31796
+ // prettier-ignore
31797
+ set(A, B, C, D, E, F, G, H) {
31798
+ this.A = A | 0;
31799
+ this.B = B | 0;
31800
+ this.C = C | 0;
31801
+ this.D = D | 0;
31802
+ this.E = E | 0;
31803
+ this.F = F | 0;
31804
+ this.G = G | 0;
31805
+ this.H = H | 0;
31806
+ }
31807
+ process(view, offset) {
31808
+ for (let i4 = 0; i4 < 16; i4++, offset += 4)
31809
+ SHA256_W2[i4] = view.getUint32(offset, false);
31810
+ for (let i4 = 16; i4 < 64; i4++) {
31811
+ const W15 = SHA256_W2[i4 - 15];
31812
+ const W2 = SHA256_W2[i4 - 2];
31813
+ const s0 = rotr2(W15, 7) ^ rotr2(W15, 18) ^ W15 >>> 3;
31814
+ const s1 = rotr2(W2, 17) ^ rotr2(W2, 19) ^ W2 >>> 10;
31815
+ SHA256_W2[i4] = s1 + SHA256_W2[i4 - 7] + s0 + SHA256_W2[i4 - 16] | 0;
31816
+ }
31817
+ let { A, B, C, D, E, F, G, H } = this;
31818
+ for (let i4 = 0; i4 < 64; i4++) {
31819
+ const sigma1 = rotr2(E, 6) ^ rotr2(E, 11) ^ rotr2(E, 25);
31820
+ const T1 = H + sigma1 + Chi2(E, F, G) + SHA256_K2[i4] + SHA256_W2[i4] | 0;
31821
+ const sigma0 = rotr2(A, 2) ^ rotr2(A, 13) ^ rotr2(A, 22);
31822
+ const T2 = sigma0 + Maj2(A, B, C) | 0;
31823
+ H = G;
31824
+ G = F;
31825
+ F = E;
31826
+ E = D + T1 | 0;
31827
+ D = C;
31828
+ C = B;
31829
+ B = A;
31830
+ A = T1 + T2 | 0;
31831
+ }
31832
+ A = A + this.A | 0;
31833
+ B = B + this.B | 0;
31834
+ C = C + this.C | 0;
31835
+ D = D + this.D | 0;
31836
+ E = E + this.E | 0;
31837
+ F = F + this.F | 0;
31838
+ G = G + this.G | 0;
31839
+ H = H + this.H | 0;
31840
+ this.set(A, B, C, D, E, F, G, H);
31841
+ }
31842
+ roundClean() {
31843
+ clean3(SHA256_W2);
31844
+ }
31845
+ destroy() {
31846
+ this.set(0, 0, 0, 0, 0, 0, 0, 0);
31847
+ clean3(this.buffer);
31848
+ }
31849
+ };
31850
+ var sha2562 = /* @__PURE__ */ createHasher2(() => new SHA256());
31851
+
31852
+ // ../../node_modules/.pnpm/@noble+hashes@1.8.0/node_modules/@noble/hashes/esm/sha256.js
31853
+ var sha2563 = sha2562;
31854
+
31855
+ // ../../node_modules/.pnpm/nostr-tools@2.23.3_typescript@5.9.3/node_modules/nostr-tools/lib/esm/utils.js
31856
+ var utf8Decoder7 = new TextDecoder("utf-8");
31857
+ var utf8Encoder7 = new TextEncoder();
31357
31858
 
31358
31859
  // ../agent/src/services/forms/types.ts
31359
31860
  var FORM_KINDS = {
@@ -31375,46 +31876,219 @@ var AnswerType = /* @__PURE__ */ ((AnswerType2) => {
31375
31876
  AnswerType2["datetime"] = "datetime";
31376
31877
  AnswerType2["fileUpload"] = "fileUpload";
31377
31878
  AnswerType2["signature"] = "signature";
31378
- AnswerType2["multiChoiceGrid"] = "multiChoiceGrid";
31879
+ AnswerType2["multipleChoiceGrid"] = "multipleChoiceGrid";
31379
31880
  AnswerType2["checkboxGrid"] = "checkboxGrid";
31881
+ AnswerType2["rating"] = "rating";
31380
31882
  AnswerType2["section"] = "section";
31381
31883
  return AnswerType2;
31382
31884
  })(AnswerType || {});
31383
31885
 
31886
+ // ../agent/src/services/forms/fieldCodec.ts
31887
+ var PRIMITIVE_BY_TYPE = {
31888
+ ["label" /* label */]: "label",
31889
+ ["section" /* section */]: "section",
31890
+ ["shortText" /* shortText */]: "text",
31891
+ ["paragraph" /* paragraph */]: "text",
31892
+ ["date" /* date */]: "text",
31893
+ ["time" /* time */]: "text",
31894
+ ["signature" /* signature */]: "text",
31895
+ ["number" /* number */]: "number",
31896
+ ["radioButton" /* radioButton */]: "option",
31897
+ ["checkboxes" /* checkboxes */]: "option",
31898
+ ["dropdown" /* dropdown */]: "option",
31899
+ ["fileUpload" /* fileUpload */]: "file",
31900
+ ["datetime" /* datetime */]: "datetime",
31901
+ ["multipleChoiceGrid" /* multipleChoiceGrid */]: "grid",
31902
+ ["checkboxGrid" /* checkboxGrid */]: "grid",
31903
+ ["rating" /* rating */]: "rating"
31904
+ };
31905
+ var DEFAULT_TYPE_BY_PRIMITIVE = {
31906
+ text: "shortText" /* shortText */,
31907
+ number: "number" /* number */,
31908
+ option: "radioButton" /* radioButton */,
31909
+ label: "label" /* label */,
31910
+ section: "section" /* section */,
31911
+ file: "fileUpload" /* fileUpload */,
31912
+ datetime: "datetime" /* datetime */,
31913
+ grid: "multipleChoiceGrid" /* multipleChoiceGrid */,
31914
+ rating: "rating" /* rating */
31915
+ };
31916
+ var GRID_TYPES = /* @__PURE__ */ new Set(["multipleChoiceGrid" /* multipleChoiceGrid */, "checkboxGrid" /* checkboxGrid */]);
31917
+ function makeId() {
31918
+ return Math.random().toString(36).slice(2, 8) || "id";
31919
+ }
31920
+ function buildFieldTag(field) {
31921
+ let options = "[]";
31922
+ if (GRID_TYPES.has(field.type)) {
31923
+ const grid = {
31924
+ rows: (field.gridRows ?? []).map((label) => [makeId(), label]),
31925
+ columns: (field.gridCols ?? []).map((label) => [makeId(), label])
31926
+ };
31927
+ options = JSON.stringify(grid);
31928
+ } else if (field.options) {
31929
+ options = JSON.stringify(field.options.map((o) => [o.id, o.label]));
31930
+ }
31931
+ const config2 = { renderElement: field.type };
31932
+ if (field.required !== void 0) config2.required = field.required;
31933
+ if (field.placeholder !== void 0) config2.placeholder = field.placeholder;
31934
+ if (field.type === "rating" /* rating */ && field.maxStars !== void 0) {
31935
+ config2.maxStars = field.maxStars;
31936
+ }
31937
+ if (GRID_TYPES.has(field.type)) {
31938
+ config2.allowMultiplePerRow = field.type === "checkboxGrid" /* checkboxGrid */;
31939
+ }
31940
+ if (field.validation) {
31941
+ const v = field.validation;
31942
+ const rules = {};
31943
+ if (v.min !== void 0) rules.min = { min: v.min };
31944
+ if (v.max !== void 0) rules.max = { max: v.max };
31945
+ if (v.regex !== void 0) rules.regex = { pattern: v.regex, errorMessage: v.regexError ?? "" };
31946
+ if (Object.keys(rules).length > 0) config2.validationRules = rules;
31947
+ }
31948
+ if (field.fileConfig) {
31949
+ const f = field.fileConfig;
31950
+ if (f.blossomServer !== void 0) config2.blossomServer = f.blossomServer;
31951
+ if (f.maxBytes !== void 0) config2.maxFileSize = f.maxBytes / (1024 * 1024);
31952
+ if (f.mimeTypes !== void 0) config2.allowedTypes = f.mimeTypes;
31953
+ }
31954
+ return ["field", field.id, PRIMITIVE_BY_TYPE[field.type], field.label, options, JSON.stringify(config2)];
31955
+ }
31956
+ function isAnswerType(value) {
31957
+ return Object.values(AnswerType).includes(value);
31958
+ }
31959
+ function resolveType(primitive, renderElement) {
31960
+ if (primitive === "section") return "section" /* section */;
31961
+ if (renderElement) {
31962
+ if (renderElement === "multiChoiceGrid") return "multipleChoiceGrid" /* multipleChoiceGrid */;
31963
+ if (isAnswerType(renderElement)) return renderElement;
31964
+ }
31965
+ if (primitive === "multiChoiceGrid") return "multipleChoiceGrid" /* multipleChoiceGrid */;
31966
+ if (isAnswerType(primitive)) return primitive;
31967
+ return DEFAULT_TYPE_BY_PRIMITIVE[primitive] ?? "shortText" /* shortText */;
31968
+ }
31969
+ function parseFieldTag(tag) {
31970
+ const [, id = "", primitive = "text", label = "", optionsJson, configJson] = tag;
31971
+ let config2 = {};
31972
+ if (configJson) {
31973
+ try {
31974
+ const parsed = JSON.parse(configJson);
31975
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) config2 = parsed;
31976
+ } catch {
31977
+ }
31978
+ }
31979
+ const field = {
31980
+ id,
31981
+ type: resolveType(primitive, config2.renderElement),
31982
+ label
31983
+ };
31984
+ let parsedOptions;
31985
+ if (optionsJson) {
31986
+ try {
31987
+ parsedOptions = JSON.parse(optionsJson);
31988
+ } catch {
31989
+ }
31990
+ }
31991
+ if (Array.isArray(parsedOptions) && parsedOptions.length > 0) {
31992
+ field.options = parsedOptions.map(
31993
+ (o) => ({ id: o[0], label: o[1] })
31994
+ );
31995
+ } else if (parsedOptions && typeof parsedOptions === "object") {
31996
+ const grid = parsedOptions;
31997
+ if (Array.isArray(grid.rows)) field.gridRows = grid.rows.map((r) => r[1]);
31998
+ if (Array.isArray(grid.columns)) field.gridCols = grid.columns.map((c) => c[1]);
31999
+ }
32000
+ if (typeof config2.required === "boolean") field.required = config2.required;
32001
+ if (typeof config2.placeholder === "string") field.placeholder = config2.placeholder;
32002
+ if (typeof config2.maxStars === "number") field.maxStars = config2.maxStars;
32003
+ const rules = config2.validationRules;
32004
+ if (rules && typeof rules === "object") {
32005
+ const validation = {};
32006
+ if (typeof rules.range?.min === "number") validation.min = rules.range.min;
32007
+ if (typeof rules.range?.max === "number") validation.max = rules.range.max;
32008
+ if (typeof rules.min?.min === "number") validation.min = rules.min.min;
32009
+ if (typeof rules.max?.max === "number") validation.max = rules.max.max;
32010
+ if (typeof rules.regex?.pattern === "string") {
32011
+ validation.regex = rules.regex.pattern;
32012
+ if (rules.regex.errorMessage) validation.regexError = rules.regex.errorMessage;
32013
+ }
32014
+ if (Object.keys(validation).length > 0) field.validation = validation;
32015
+ }
32016
+ if (config2.blossomServer !== void 0 || config2.maxFileSize !== void 0 || config2.allowedTypes !== void 0) {
32017
+ field.fileConfig = {
32018
+ ...typeof config2.blossomServer === "string" && { blossomServer: config2.blossomServer },
32019
+ ...typeof config2.maxFileSize === "number" && {
32020
+ maxBytes: config2.maxFileSize * 1024 * 1024
32021
+ },
32022
+ ...Array.isArray(config2.allowedTypes) && { mimeTypes: config2.allowedTypes }
32023
+ };
32024
+ }
32025
+ return field;
32026
+ }
32027
+
32028
+ // ../agent/src/services/forms/keys.ts
32029
+ function makeViewKeySigner(viewKeyHex) {
32030
+ return new LocalSigner2(hexToBytes3(viewKeyHex));
32031
+ }
32032
+ function makeSigningKeySigner(signingKeyHex) {
32033
+ return new LocalSigner2(hexToBytes3(signingKeyHex));
32034
+ }
32035
+ function encodeFormKeys(signingKeyHex, viewKeyHex) {
32036
+ return viewKeyHex ? `${signingKeyHex}:${viewKeyHex}` : signingKeyHex;
32037
+ }
32038
+ function decodeFormKeys(segment) {
32039
+ const idx = segment.indexOf(":");
32040
+ if (idx < 0) return { signingKey: segment };
32041
+ return { signingKey: segment.slice(0, idx), viewKey: segment.slice(idx + 1) };
32042
+ }
32043
+ function hexToBytes3(hex) {
32044
+ if (hex.length % 2 !== 0) throw new Error("invalid hex length");
32045
+ if (hex.length > 0 && !/^[0-9a-fA-F]+$/.test(hex)) throw new Error("invalid hex characters");
32046
+ const out = new Uint8Array(hex.length / 2);
32047
+ for (let i4 = 0; i4 < out.length; i4++) {
32048
+ out[i4] = parseInt(hex.slice(i4 * 2, i4 * 2 + 2), 16);
32049
+ }
32050
+ return out;
32051
+ }
32052
+
31384
32053
  // ../agent/src/services/forms/service.ts
32054
+ function relaysForForm(formRelays) {
32055
+ return Array.from(/* @__PURE__ */ new Set([...relayManager.getRelaysForModule("forms"), ...formRelays ?? []]));
32056
+ }
32057
+ function buildSpecRows(formId, name, fields, settings) {
32058
+ const rows = [
32059
+ ["d", formId],
32060
+ ["name", name]
32061
+ ];
32062
+ if (settings) rows.push(["settings", JSON.stringify(settings)]);
32063
+ for (const field of fields) rows.push(buildFieldTag(field));
32064
+ return rows;
32065
+ }
32066
+ function buildEncryptedOuterTags(formId, name, relays, settings) {
32067
+ const tags = [["d", formId], ["name", name], ...relays.map((r) => ["relay", r])];
32068
+ const allowed = settings?.allowedResponders ?? [];
32069
+ const pTags = /* @__PURE__ */ new Set([...allowed, ...settings?.collaborators ?? []]);
32070
+ for (const pk of allowed) tags.push(["allowed", pk]);
32071
+ for (const pk of pTags) tags.push(["p", pk]);
32072
+ return tags;
32073
+ }
31385
32074
  async function createForm(params) {
31386
- const signer = await signerManager.getSigner();
31387
- const userPubkey = await signer.getPublicKey();
32075
+ await signerManager.getSigner();
31388
32076
  const formId = crypto.randomUUID().slice(0, 8);
31389
32077
  const relays = relayManager.getRelaysForModule("forms");
31390
- const baseTags = [
31391
- ["d", formId],
31392
- ["name", params.name]
31393
- ];
31394
- if (params.settings) baseTags.push(["settings", JSON.stringify(params.settings)]);
31395
- for (const field of params.fields) {
31396
- baseTags.push(buildFieldTag(field));
31397
- }
32078
+ const specRows = buildSpecRows(formId, params.name, params.fields, params.settings);
32079
+ const signingKey = generateSecretKey();
32080
+ const signingKeyHex = bytesToHex(signingKey);
32081
+ const signingPubkey = getPublicKey(signingKey);
31398
32082
  if (params.encrypt) {
31399
- const signingKey = generateSecretKey();
31400
- const signingKeyHex = bytesToHex(signingKey);
31401
- const signingPubkey = getPublicKey(signingKey);
31402
32083
  const viewKey = generateSecretKey();
31403
32084
  const viewKeyHex = bytesToHex(viewKey);
31404
32085
  const viewPubkey = getPublicKey(viewKey);
31405
32086
  const formSigner = new LocalSigner2(signingKey);
31406
- const fieldTags = baseTags.filter((t) => t[0] === "field");
31407
- const content = await formSigner.nip44Encrypt(viewPubkey, JSON.stringify(fieldTags));
31408
- const encTags = [
31409
- ["d", formId],
31410
- ["name", params.name],
31411
- ["encryption", "view-key"]
31412
- ];
31413
- if (params.settings) encTags.push(["settings", JSON.stringify(params.settings)]);
32087
+ const content = await formSigner.nip44Encrypt(viewPubkey, JSON.stringify(specRows));
31414
32088
  const event2 = {
31415
32089
  kind: FORM_KINDS.template,
31416
32090
  created_at: Math.floor(Date.now() / 1e3),
31417
- tags: encTags,
32091
+ tags: buildEncryptedOuterTags(formId, params.name, relays, params.settings),
31418
32092
  content
31419
32093
  };
31420
32094
  const signed2 = finalizeEvent(event2, signingKey);
@@ -31422,20 +32096,20 @@ async function createForm(params) {
31422
32096
  await appendToMyFormsList(signingPubkey, formId, relays[0] ?? "", signingKeyHex, viewKeyHex);
31423
32097
  return { formId, pubkey: signingPubkey, signingKey: signingKeyHex, viewKey: viewKeyHex };
31424
32098
  }
31425
- if (params.settings?.publicForm) baseTags.push(["t", "public"]);
32099
+ const tags = [...specRows, ["t", "public"], ...relays.map((r) => ["relay", r])];
31426
32100
  const event = {
31427
32101
  kind: FORM_KINDS.template,
31428
32102
  created_at: Math.floor(Date.now() / 1e3),
31429
- tags: baseTags,
32103
+ tags,
31430
32104
  content: ""
31431
32105
  };
31432
- const signed = await signer.signEvent(event);
32106
+ const signed = finalizeEvent(event, signingKey);
31433
32107
  await nostrRuntime.publish(relays, signed);
31434
- await appendToMyFormsList(userPubkey, formId, relays[0] ?? "", void 0, void 0);
31435
- return { formId, pubkey: userPubkey };
32108
+ await appendToMyFormsList(signingPubkey, formId, relays[0] ?? "", signingKeyHex, void 0);
32109
+ return { formId, pubkey: signingPubkey, signingKey: signingKeyHex };
31436
32110
  }
31437
- async function fetchForm(pubkey, formId, viewKey) {
31438
- const relays = relayManager.getRelaysForModule("forms");
32111
+ async function fetchForm(pubkey, formId, viewKey, relayHints) {
32112
+ const relays = relaysForForm(relayHints);
31439
32113
  const event = await nostrRuntime.fetchOne(relays, {
31440
32114
  kinds: [FORM_KINDS.template],
31441
32115
  authors: [pubkey],
@@ -31443,25 +32117,48 @@ async function fetchForm(pubkey, formId, viewKey) {
31443
32117
  limit: 1
31444
32118
  });
31445
32119
  if (!event) return null;
31446
- const template = parseFormEvent(event);
31447
- if (template.isEncrypted && event.content && viewKey) {
32120
+ let effectiveViewKey = viewKey;
32121
+ if (!effectiveViewKey && event.content && !event.tags.some((t) => t[0] === "field")) {
32122
+ const keys = await fetchFormKeys(pubkey, formId).catch(() => null);
32123
+ effectiveViewKey = keys?.viewKey;
32124
+ }
32125
+ let decryptedRows;
32126
+ if (event.content && effectiveViewKey) {
31448
32127
  try {
31449
- const viewSigner = makeViewKeySigner(viewKey);
32128
+ const viewSigner = makeViewKeySigner(effectiveViewKey);
31450
32129
  const decrypted = await viewSigner.nip44Decrypt(pubkey, event.content);
31451
- const fieldTags = JSON.parse(decrypted);
31452
- template.fields = fieldTags.filter((t) => t[0] === "field").map((t) => ({
31453
- id: t[1],
31454
- type: t[2],
31455
- label: t[3],
31456
- options: t[4] ? safeParseOptions(t[4]) : void 0,
31457
- required: t[5] ? JSON.parse(t[5])?.required : void 0
31458
- }));
32130
+ const rows = JSON.parse(decrypted);
32131
+ if (Array.isArray(rows)) decryptedRows = rows;
31459
32132
  } catch {
31460
32133
  }
31461
32134
  }
31462
- return template;
32135
+ return parseFormEvent(event, decryptedRows);
32136
+ }
32137
+ function accessGrantAlias(formAuthor, formId, recipient) {
32138
+ return bytesToHex(sha2563(`${FORM_KINDS.template}:${formAuthor}:${formId}:${recipient}`));
32139
+ }
32140
+ async function fetchFormKeys(formAuthor, formId) {
32141
+ const signer = signerManager.getSignerIfAvailable();
32142
+ if (!signer?.nip44Decrypt) return null;
32143
+ const userPub = await signer.getPublicKey();
32144
+ const relays = relayManager.getRelaysForModule("forms");
32145
+ const wraps = await nostrRuntime.querySync(relays, {
32146
+ kinds: [FORM_KINDS.giftWrap],
32147
+ "#p": [accessGrantAlias(formAuthor, formId, userPub)]
32148
+ });
32149
+ for (const wrap of wraps) {
32150
+ try {
32151
+ const seal = JSON.parse(await signer.nip44Decrypt(wrap.pubkey, wrap.content));
32152
+ const rumor = JSON.parse(await signer.nip44Decrypt(seal.pubkey, seal.content));
32153
+ const viewKey = rumor.tags?.find((t) => t[0] === "ViewAccess")?.[1];
32154
+ const signingKey = rumor.tags?.find((t) => t[0] === "EditAccess")?.[1];
32155
+ if (viewKey || signingKey) return { viewKey, signingKey };
32156
+ } catch {
32157
+ }
32158
+ }
32159
+ return null;
31463
32160
  }
31464
- async function submitResponse(formPubkey, formId, responses, encrypt7 = false, overrideSigner) {
32161
+ async function submitResponse(formPubkey, formId, responses, encrypt7 = false, overrideSigner, formRelays) {
31465
32162
  const signer = overrideSigner ?? await signerManager.getSigner();
31466
32163
  const responseTags = responses.map((r) => ["response", r.fieldId, r.answer, r.metadata ?? ""]);
31467
32164
  const tags = [
@@ -31480,11 +32177,10 @@ async function submitResponse(formPubkey, formId, responses, encrypt7 = false, o
31480
32177
  content
31481
32178
  };
31482
32179
  const signed = await signer.signEvent(event);
31483
- const relays = relayManager.getRelaysForModule("forms");
31484
- await nostrRuntime.publish(relays, signed);
32180
+ await nostrRuntime.publish(relaysForForm(formRelays), signed);
31485
32181
  }
31486
- function subscribeToResponses(formPubkey, formId, onResponse, onEose, signingKey) {
31487
- const relays = relayManager.getRelaysForModule("forms");
32182
+ function subscribeToResponses(formPubkey, formId, onResponse, onEose, signingKey, formRelays) {
32183
+ const relays = relaysForForm(formRelays);
31488
32184
  const formSigner = signingKey ? makeSigningKeySigner(signingKey) : void 0;
31489
32185
  return nostrRuntime.subscribe(
31490
32186
  relays,
@@ -31508,9 +32204,8 @@ function subscribeToResponses(formPubkey, formId, onResponse, onEose, signingKey
31508
32204
  }
31509
32205
  );
31510
32206
  }
31511
- async function fetchResponses(formPubkey, formId, signingKey) {
31512
- const relays = relayManager.getRelaysForModule("forms");
31513
- const events = await nostrRuntime.querySync(relays, {
32207
+ async function fetchResponses(formPubkey, formId, signingKey, formRelays) {
32208
+ const events = await nostrRuntime.querySync(relaysForForm(formRelays), {
31514
32209
  kinds: [FORM_KINDS.response],
31515
32210
  "#a": [`${FORM_KINDS.template}:${formPubkey}:${formId}`]
31516
32211
  });
@@ -31543,6 +32238,27 @@ async function fetchLatestMyFormsEvent(relays, userPubkey) {
31543
32238
  null
31544
32239
  );
31545
32240
  }
32241
+ async function decryptListEntries(signer, userPubkey, content) {
32242
+ let decrypted = "";
32243
+ if (content.includes("?iv=")) {
32244
+ if (!signer.decrypt) throw new Error("Signer cannot decrypt");
32245
+ decrypted = await signer.decrypt(userPubkey, content);
32246
+ } else {
32247
+ decrypted = await nip44SelfDecrypt(signer, content);
32248
+ }
32249
+ const parsed = JSON.parse(decrypted);
32250
+ return Array.isArray(parsed) ? parsed : [];
32251
+ }
32252
+ async function publishMyFormsList(signer, relays, entries) {
32253
+ const encrypted = await nip44SelfEncrypt(signer, JSON.stringify(entries));
32254
+ const event = {
32255
+ kind: FORM_KINDS.myFormsList,
32256
+ created_at: Math.floor(Date.now() / 1e3),
32257
+ tags: [],
32258
+ content: encrypted
32259
+ };
32260
+ await nostrRuntime.publish(relays, await signer.signEvent(event));
32261
+ }
31546
32262
  async function fetchMyForms() {
31547
32263
  const signer = await signerManager.getSigner();
31548
32264
  const userPubkey = await signer.getPublicKey();
@@ -31551,15 +32267,7 @@ async function fetchMyForms() {
31551
32267
  let entries = [];
31552
32268
  if (listEvent?.content) {
31553
32269
  try {
31554
- let decrypted = "";
31555
- if (listEvent.content.includes("?iv=")) {
31556
- if (!signer.decrypt) throw new Error("Signer cannot decrypt");
31557
- decrypted = await signer.decrypt(userPubkey, listEvent.content);
31558
- } else {
31559
- decrypted = await nip44SelfDecrypt(signer, listEvent.content);
31560
- }
31561
- const parsed = JSON.parse(decrypted);
31562
- entries = Array.isArray(parsed) ? parsed : [];
32270
+ entries = await decryptListEntries(signer, userPubkey, listEvent.content);
31563
32271
  } catch {
31564
32272
  }
31565
32273
  }
@@ -31577,7 +32285,7 @@ async function fetchMyForms() {
31577
32285
  if (d) eventMap.set(`${evt.pubkey}:${d}`, evt);
31578
32286
  }
31579
32287
  return entries.filter((e) => e[0] === "f" && e[1]).map((entry) => {
31580
- const [, coordKey, , keySegment] = entry;
32288
+ const [, coordKey, relayHint, keySegment] = entry;
31581
32289
  const [formPubkey, formId] = coordKey.split(":");
31582
32290
  if (!formPubkey || !formId) return null;
31583
32291
  const evt = eventMap.get(coordKey);
@@ -31593,7 +32301,8 @@ async function fetchMyForms() {
31593
32301
  createdAt: evt?.created_at ?? 0,
31594
32302
  isEncrypted,
31595
32303
  signingKey: keys?.signingKey,
31596
- viewKey: keys?.viewKey
32304
+ viewKey: keys?.viewKey,
32305
+ relay: relayHint || void 0
31597
32306
  };
31598
32307
  return summary;
31599
32308
  }).filter((s) => s !== null);
@@ -31606,15 +32315,7 @@ async function appendToMyFormsList(formPubkey, formId, relay, signingKeyHex, vie
31606
32315
  let entries = [];
31607
32316
  if (existing?.content) {
31608
32317
  try {
31609
- let decrypted = "";
31610
- if (existing.content.includes("?iv=")) {
31611
- if (!signer.decrypt) throw new Error("Signer cannot decrypt");
31612
- decrypted = await signer.decrypt(userPubkey, existing.content);
31613
- } else {
31614
- decrypted = await nip44SelfDecrypt(signer, existing.content);
31615
- }
31616
- const parsed = JSON.parse(decrypted);
31617
- entries = Array.isArray(parsed) ? parsed : [];
32318
+ entries = await decryptListEntries(signer, userPubkey, existing.content);
31618
32319
  } catch {
31619
32320
  entries = [];
31620
32321
  }
@@ -31627,15 +32328,7 @@ async function appendToMyFormsList(formPubkey, formId, relay, signingKeyHex, vie
31627
32328
  const secrets = signingKeyHex ? encodeFormKeys(signingKeyHex, viewKeyHex) : "";
31628
32329
  entries.push(["f", coordKey, relay, secrets]);
31629
32330
  }
31630
- const encrypted = await nip44SelfEncrypt(signer, JSON.stringify(entries));
31631
- const listEvent = {
31632
- kind: FORM_KINDS.myFormsList,
31633
- created_at: Math.floor(Date.now() / 1e3),
31634
- tags: [],
31635
- content: encrypted
31636
- };
31637
- const signed = await signer.signEvent(listEvent);
31638
- await nostrRuntime.publish(relays, signed);
32331
+ await publishMyFormsList(signer, relays, entries);
31639
32332
  }
31640
32333
  async function saveToMyForms(summaries) {
31641
32334
  const signer = await signerManager.getSigner();
@@ -31643,18 +32336,10 @@ async function saveToMyForms(summaries) {
31643
32336
  const entries = summaries.map((s) => [
31644
32337
  "f",
31645
32338
  `${s.pubkey}:${s.id}`,
31646
- "",
32339
+ s.relay ?? "",
31647
32340
  s.signingKey ? encodeFormKeys(s.signingKey, s.viewKey) : ""
31648
32341
  ]);
31649
- const encrypted = await nip44SelfEncrypt(signer, JSON.stringify(entries));
31650
- const event = {
31651
- kind: FORM_KINDS.myFormsList,
31652
- created_at: Math.floor(Date.now() / 1e3),
31653
- tags: [],
31654
- content: encrypted
31655
- };
31656
- const signed = await signer.signEvent(event);
31657
- await nostrRuntime.publish(relays, signed);
32342
+ await publishMyFormsList(signer, relays, entries);
31658
32343
  }
31659
32344
  async function fetchMyFormsByAuthor(pubkey, relays) {
31660
32345
  const filter = {
@@ -31677,6 +32362,8 @@ async function fetchMyFormsByAuthor(pubkey, relays) {
31677
32362
  }
31678
32363
  async function deleteForm(formId, formPubkey) {
31679
32364
  const signer = await signerManager.getSigner();
32365
+ const userPubkey = await signer.getPublicKey();
32366
+ const relays = relayManager.getRelaysForModule("forms");
31680
32367
  const coordinate = `${FORM_KINDS.template}:${formPubkey}:${formId}`;
31681
32368
  const event = {
31682
32369
  kind: 5,
@@ -31687,58 +32374,60 @@ async function deleteForm(formId, formPubkey) {
31687
32374
  ],
31688
32375
  content: "Deleted via Formstr"
31689
32376
  };
31690
- const signed = await signer.signEvent(event);
31691
- const relays = relayManager.getRelaysForModule("forms");
31692
- await nostrRuntime.publish(relays, signed);
32377
+ await nostrRuntime.publish(relays, await signer.signEvent(event));
32378
+ const listEvent = await fetchLatestMyFormsEvent(relays, userPubkey);
32379
+ if (!listEvent?.content) return;
32380
+ let entries;
32381
+ try {
32382
+ entries = await decryptListEntries(signer, userPubkey, listEvent.content);
32383
+ } catch {
32384
+ return;
32385
+ }
32386
+ const coordKey = `${formPubkey}:${formId}`;
32387
+ const trimmed = entries.filter((e) => e[1] !== coordKey);
32388
+ if (trimmed.length === entries.length) return;
32389
+ await publishMyFormsList(signer, relays, trimmed);
31693
32390
  }
31694
32391
  async function updateForm(params) {
31695
32392
  const relays = relayManager.getRelaysForModule("forms");
31696
- const existing = await fetchForm(params.pubkey, params.formId);
32393
+ const summary = (await fetchMyForms()).find(
32394
+ (f) => f.id === params.formId && f.pubkey === params.pubkey
32395
+ );
32396
+ const existing = await fetchForm(params.pubkey, params.formId, summary?.viewKey);
31697
32397
  if (!existing) throw new Error(`Form not found: ${params.formId}`);
31698
32398
  const name = params.name ?? existing.name;
31699
32399
  const settings = { ...existing.settings, ...params.settings };
31700
32400
  const fields = params.fields ?? existing.fields;
32401
+ const specRows = buildSpecRows(params.formId, name, fields, settings);
31701
32402
  if (existing.isEncrypted) {
31702
- const summary = (await fetchMyForms()).find(
31703
- (f) => f.id === params.formId && f.pubkey === params.pubkey
31704
- );
31705
32403
  if (!summary?.signingKey || !summary?.viewKey) {
31706
32404
  throw new Error("Not the form owner or signing key unavailable");
31707
32405
  }
31708
32406
  const signingKeyBytes = hexToBytes3(summary.signingKey);
31709
32407
  const formSigner = makeSigningKeySigner(summary.signingKey);
31710
32408
  const viewPubkey = getPublicKey(hexToBytes3(summary.viewKey));
31711
- const fieldTags = fields.map(buildFieldTag);
31712
- const content = await formSigner.nip44Encrypt(viewPubkey, JSON.stringify(fieldTags));
31713
- const tags2 = [
31714
- ["d", params.formId],
31715
- ["name", name],
31716
- ["encryption", "view-key"],
31717
- ["settings", JSON.stringify(settings)]
31718
- ];
32409
+ const content = await formSigner.nip44Encrypt(viewPubkey, JSON.stringify(specRows));
31719
32410
  const event2 = {
31720
32411
  kind: FORM_KINDS.template,
31721
32412
  created_at: Math.floor(Date.now() / 1e3),
31722
- tags: tags2,
32413
+ tags: buildEncryptedOuterTags(params.formId, name, relays, settings),
31723
32414
  content
31724
32415
  };
31725
32416
  await nostrRuntime.publish(relays, finalizeEvent(event2, signingKeyBytes));
31726
32417
  return;
31727
32418
  }
31728
- const signer = await signerManager.getSigner();
31729
- const tags = [
31730
- ["d", params.formId],
31731
- ["name", name],
31732
- ["settings", JSON.stringify(settings)]
31733
- ];
31734
- for (const field of fields) tags.push(buildFieldTag(field));
31735
- if (settings.publicForm) tags.push(["t", "public"]);
32419
+ const tags = [...specRows, ["t", "public"], ...relays.map((r) => ["relay", r])];
31736
32420
  const event = {
31737
32421
  kind: FORM_KINDS.template,
31738
32422
  created_at: Math.floor(Date.now() / 1e3),
31739
32423
  tags,
31740
32424
  content: ""
31741
32425
  };
32426
+ if (summary?.signingKey) {
32427
+ await nostrRuntime.publish(relays, finalizeEvent(event, hexToBytes3(summary.signingKey)));
32428
+ return;
32429
+ }
32430
+ const signer = await signerManager.getSigner();
31742
32431
  await nostrRuntime.publish(relays, await signer.signEvent(event));
31743
32432
  }
31744
32433
  async function shareForm(params) {
@@ -31746,27 +32435,50 @@ async function shareForm(params) {
31746
32435
  const summary = (await fetchMyForms()).find(
31747
32436
  (f) => f.id === params.formId && f.pubkey === params.formPubkey
31748
32437
  );
31749
- if (!summary?.viewKey) {
31750
- throw new Error("Not the form owner or view key unavailable");
31751
- }
31752
- const coordinate = `${FORM_KINDS.template}:${params.formPubkey}:${params.formId}`;
31753
- const naddr = createRef("forms", FORM_KINDS.template, params.formPubkey, params.formId);
31754
- const signer = await signerManager.getSigner();
31755
- const rumor = {
31756
- kind: 14,
31757
- created_at: Math.floor(Date.now() / 1e3),
31758
- tags: [
31759
- ["a", coordinate],
31760
- ["viewKey", summary.viewKey]
31761
- ],
31762
- content: `Formstr form view key for ${naddr}`
31763
- };
32438
+ if (!summary?.signingKey) {
32439
+ throw new Error("Not the form owner or form keys unavailable");
32440
+ }
32441
+ const signingKeyBytes = hexToBytes3(summary.signingKey);
32442
+ const signingPubkey = getPublicKey(signingKeyBytes);
32443
+ const formSigner = new LocalSigner2(signingKeyBytes);
32444
+ const now2 = () => Math.round(Date.now() / 1e3);
32445
+ const editors = new Set(params.editors ?? []);
32446
+ const targets = [.../* @__PURE__ */ new Set([...params.recipients, ...editors])];
31764
32447
  const failed = [];
31765
32448
  let published = 0;
31766
- for (const recipient of params.recipients) {
32449
+ for (const recipient of targets) {
31767
32450
  try {
31768
- const wraps = await wrapManyEvents3(rumor, signer, [recipient]);
31769
- for (const wrap of wraps) await nostrRuntime.publish(relays, wrap);
32451
+ const accessTags = [];
32452
+ if (editors.has(recipient)) accessTags.push(["EditAccess", summary.signingKey]);
32453
+ if (summary.viewKey) accessTags.push(["ViewAccess", summary.viewKey]);
32454
+ const rumor = {
32455
+ kind: 18,
32456
+ pubkey: signingPubkey,
32457
+ created_at: now2(),
32458
+ content: "",
32459
+ tags: accessTags
32460
+ };
32461
+ rumor.id = getEventHash(rumor);
32462
+ const seal = finalizeEvent(
32463
+ {
32464
+ kind: 13,
32465
+ content: await formSigner.nip44Encrypt(recipient, JSON.stringify(rumor)),
32466
+ created_at: now2(),
32467
+ tags: []
32468
+ },
32469
+ signingKeyBytes
32470
+ );
32471
+ const randomKey = generateSecretKey();
32472
+ const wrap = finalizeEvent(
32473
+ {
32474
+ kind: 1059,
32475
+ content: await new LocalSigner2(randomKey).nip44Encrypt(recipient, JSON.stringify(seal)),
32476
+ created_at: now2(),
32477
+ tags: [["p", accessGrantAlias(signingPubkey, params.formId, recipient)]]
32478
+ },
32479
+ randomKey
32480
+ );
32481
+ await nostrRuntime.publish(relays, wrap);
31770
32482
  published++;
31771
32483
  } catch {
31772
32484
  failed.push(recipient);
@@ -31790,32 +32502,32 @@ async function importForm(summary) {
31790
32502
  if (current.some((f) => f.id === summary.id && f.pubkey === summary.pubkey)) return;
31791
32503
  await saveToMyForms([...current, summary]);
31792
32504
  }
31793
- function buildFieldTag(field) {
31794
- const options = field.options ? JSON.stringify(field.options.map((o) => [o.id, o.label])) : "[]";
31795
- const config2 = JSON.stringify({ required: field.required, placeholder: field.placeholder });
31796
- return ["field", field.id, field.type, field.label, options, config2];
31797
- }
31798
- function parseFormEvent(event) {
31799
- const dTag = event.tags.find((t) => t[0] === "d")?.[1] ?? "";
31800
- const nameTag = event.tags.find((t) => t[0] === "name")?.[1] ?? "Untitled";
31801
- const settingsTag = event.tags.find((t) => t[0] === "settings")?.[1];
32505
+ function parseFormEvent(event, decryptedRows) {
32506
+ const merged = decryptedRows ? [...event.tags, ...decryptedRows] : event.tags;
32507
+ const dTag = merged.find((t) => t[0] === "d")?.[1] ?? "";
32508
+ const nameTag = merged.find((t) => t[0] === "name")?.[1] ?? "Untitled";
32509
+ const settingsTag = merged.find((t) => t[0] === "settings")?.[1];
31802
32510
  const encTag = event.tags.find((t) => t[0] === "encryption")?.[1];
31803
- const fields = event.tags.filter((t) => t[0] === "field").map((t) => ({
31804
- id: t[1],
31805
- type: t[2],
31806
- label: t[3],
31807
- options: t[4] ? safeParseOptions(t[4]) : void 0,
31808
- required: t[5] ? JSON.parse(t[5])?.required : void 0
31809
- }));
31810
- const isEncrypted = encTag != null ? encTag === "view-key" : event.content.length > 0 && fields.length === 0;
32511
+ const relays = event.tags.filter((t) => t[0] === "relay" && t[1]).map((t) => t[1]);
32512
+ const fields = merged.filter((t) => t[0] === "field").map(parseFieldTag);
32513
+ const outerHasFields = event.tags.some((t) => t[0] === "field");
32514
+ const isEncrypted = encTag != null ? encTag === "view-key" : event.content.length > 0 && !outerHasFields;
32515
+ let settings = {};
32516
+ if (settingsTag) {
32517
+ try {
32518
+ settings = JSON.parse(settingsTag);
32519
+ } catch {
32520
+ }
32521
+ }
31811
32522
  return {
31812
32523
  id: dTag,
31813
32524
  name: nameTag,
31814
32525
  fields,
31815
- settings: settingsTag ? JSON.parse(settingsTag) : {},
32526
+ settings,
31816
32527
  pubkey: event.pubkey,
31817
32528
  createdAt: event.created_at,
31818
32529
  isEncrypted,
32530
+ ...relays.length > 0 && { relays },
31819
32531
  event
31820
32532
  };
31821
32533
  }
@@ -31831,15 +32543,6 @@ function parseResponseEvent(event) {
31831
32543
  event
31832
32544
  };
31833
32545
  }
31834
- function safeParseOptions(json) {
31835
- try {
31836
- const arr = JSON.parse(json);
31837
- if (!Array.isArray(arr)) return void 0;
31838
- return arr.map((o) => ({ id: o[0], label: o[1] }));
31839
- } catch {
31840
- return void 0;
31841
- }
31842
- }
31843
32546
 
31844
32547
  // ../agent/src/services/calendar/service.ts
31845
32548
  var service_exports2 = {};
@@ -31854,9 +32557,14 @@ __export(service_exports2, {
31854
32557
  fetchCalendarLists: () => fetchCalendarLists,
31855
32558
  fetchDeletions: () => fetchDeletions,
31856
32559
  fetchInvitationsSync: () => fetchInvitationsSync,
32560
+ fetchParticipantRemovals: () => fetchParticipantRemovals,
32561
+ fetchRelayListsForPubkeys: () => fetchRelayListsForPubkeys,
32562
+ getInvitationInboxRelays: () => getInvitationInboxRelays,
31857
32563
  isEventDeleted: () => isEventDeleted,
32564
+ lookupEventViewKey: () => lookupEventViewKey,
31858
32565
  moveEventBetweenCalendarLists: () => moveEventBetweenCalendarLists,
31859
32566
  parseCalendarEvent: () => parseCalendarEvent,
32567
+ publishParticipantRemovalEvent: () => publishParticipantRemovalEvent,
31860
32568
  publishPrivateCalendarEvent: () => publishPrivateCalendarEvent,
31861
32569
  publishPublicCalendarEvent: () => publishPublicCalendarEvent,
31862
32570
  removeEventFromCalendarList: () => removeEventFromCalendarList,
@@ -31873,6 +32581,7 @@ function encodeCalendarList(list) {
31873
32581
  ["content", list.description ?? ""],
31874
32582
  ["color", list.color]
31875
32583
  ];
32584
+ if (list.notificationPreference === "disabled") tags.push(["notifications", "disabled"]);
31876
32585
  for (const ref of list.eventRefs) tags.push(["a", ...ref]);
31877
32586
  return tags;
31878
32587
  }
@@ -31880,6 +32589,7 @@ function decodeCalendarList(tags, dTag, eventId) {
31880
32589
  let title = DEFAULT_TITLE;
31881
32590
  let description = "";
31882
32591
  let color = DEFAULT_COLOR;
32592
+ let notificationPreference;
31883
32593
  const eventRefs = [];
31884
32594
  for (const tag of tags) {
31885
32595
  if (!Array.isArray(tag) || tag.length === 0) continue;
@@ -31893,6 +32603,9 @@ function decodeCalendarList(tags, dTag, eventId) {
31893
32603
  case "color":
31894
32604
  color = tag[1] || DEFAULT_COLOR;
31895
32605
  break;
32606
+ case "notifications":
32607
+ notificationPreference = tag[1] === "disabled" ? "disabled" : "enabled";
32608
+ break;
31896
32609
  case "a":
31897
32610
  if (tag[1] === "a") {
31898
32611
  const coord = tag[2] ?? "";
@@ -31911,7 +32624,8 @@ function decodeCalendarList(tags, dTag, eventId) {
31911
32624
  color,
31912
32625
  eventRefs,
31913
32626
  createdAt: 0,
31914
- isVisible: true
32627
+ isVisible: true,
32628
+ notificationPreference
31915
32629
  };
31916
32630
  }
31917
32631
 
@@ -31936,6 +32650,8 @@ var CALENDAR_KINDS = {
31936
32650
  rsvpGiftWrap: 1055,
31937
32651
  rsvpRumor: 55,
31938
32652
  participantRemoval: 84,
32653
+ /** Public free/busy list, one per (user, YYYY-MM month). */
32654
+ publicBusyList: 31926,
31939
32655
  // Appointment scheduling (Calendly-style booking links).
31940
32656
  schedulingPage: 31927,
31941
32657
  schedulingPagesList: 32680,
@@ -32181,13 +32897,14 @@ async function publishPrivateCalendarEvent(draft, calendarId) {
32181
32897
  const eventData = [
32182
32898
  ["title", draft.title],
32183
32899
  ["description", draft.description],
32184
- ["start", String(Math.floor(draft.begin.getTime() / 1e3))],
32185
- ["end", String(Math.floor(draft.end.getTime() / 1e3))],
32900
+ ["start", Math.floor(draft.begin.getTime() / 1e3)],
32901
+ ["end", Math.floor(draft.end.getTime() / 1e3)],
32186
32902
  ["d", eventId]
32187
32903
  ];
32188
32904
  if (draft.image) eventData.push(["image", draft.image]);
32189
32905
  if (draft.location) eventData.push(["location", draft.location]);
32190
32906
  for (const cat of draft.categories ?? []) eventData.push(["t", cat]);
32907
+ eventData.push(["p", pubkey]);
32191
32908
  for (const p of draft.participants ?? []) eventData.push(["p", p]);
32192
32909
  if (draft.startTzid) eventData.push(["start_tzid", draft.startTzid]);
32193
32910
  if (draft.endTzid) eventData.push(["end_tzid", draft.endTzid]);
@@ -32195,7 +32912,12 @@ async function publishPrivateCalendarEvent(draft, calendarId) {
32195
32912
  eventData.push(["L", "rrule"]);
32196
32913
  eventData.push(["l", draft.rrule]);
32197
32914
  }
32198
- if (draft.registrationFormRef) eventData.push(["form", draft.registrationFormRef]);
32915
+ if (draft.notificationPreference) eventData.push(["notification", draft.notificationPreference]);
32916
+ if (draft.registrationFormRef) {
32917
+ eventData.push(
32918
+ draft.registrationFormViewKey ? ["form", draft.registrationFormRef, draft.registrationFormViewKey] : ["form", draft.registrationFormRef]
32919
+ );
32920
+ }
32199
32921
  const viewKeyNsec = draft.viewKey ?? generateViewKey().nsec;
32200
32922
  const content = await encryptWithViewKey(viewKeyNsec, JSON.stringify(eventData));
32201
32923
  const event = {
@@ -32210,6 +32932,7 @@ async function publishPrivateCalendarEvent(draft, calendarId) {
32210
32932
  const relayHint = relays[0] ?? "";
32211
32933
  const coordinate = `${CALENDAR_KINDS.privateEvent}:${pubkey}:${eventId}`;
32212
32934
  if (draft.participants?.length) {
32935
+ const relayLists = await fetchRelayListsForPubkeys(draft.participants);
32213
32936
  for (const participant of draft.participants) {
32214
32937
  const wrap = await wrapEvent3(
32215
32938
  {
@@ -32224,7 +32947,7 @@ async function publishPrivateCalendarEvent(draft, calendarId) {
32224
32947
  participant,
32225
32948
  CALENDAR_KINDS.giftWrap
32226
32949
  );
32227
- await nostrRuntime.publish(relays, wrap);
32950
+ await nostrRuntime.publish(relayLists.get(participant) ?? relays, wrap);
32228
32951
  }
32229
32952
  }
32230
32953
  return {
@@ -32250,9 +32973,37 @@ async function publishPrivateCalendarEvent(draft, calendarId) {
32250
32973
  startTzid: draft.startTzid,
32251
32974
  endTzid: draft.endTzid,
32252
32975
  registrationFormRef: draft.registrationFormRef,
32976
+ registrationFormViewKey: draft.registrationFormViewKey,
32977
+ notificationPreference: draft.notificationPreference,
32253
32978
  event: signed
32254
32979
  };
32255
32980
  }
32981
+ async function fetchRelayListsForPubkeys(pubkeys) {
32982
+ const result = /* @__PURE__ */ new Map();
32983
+ if (pubkeys.length === 0) return result;
32984
+ const relays = relayManager.getRelaysForModule("calendar");
32985
+ const events = await nostrRuntime.querySync(relays, { kinds: [10002], authors: pubkeys });
32986
+ const newest = /* @__PURE__ */ new Map();
32987
+ for (const event of events) {
32988
+ const prev = newest.get(event.pubkey);
32989
+ if (!prev || event.created_at > prev.created_at) newest.set(event.pubkey, event);
32990
+ }
32991
+ for (const [pubkey, event] of newest) {
32992
+ const urls = event.tags.filter((t) => t[0] === "r" && t[1]).map((t) => t[1]);
32993
+ if (urls.length > 0) result.set(pubkey, urls);
32994
+ }
32995
+ return result;
32996
+ }
32997
+ async function getInvitationInboxRelays(pubkey) {
32998
+ const relays = relayManager.getRelaysForModule("calendar");
32999
+ try {
33000
+ const configs = await relayManager.fetchUserRelays(pubkey);
33001
+ const readRelays = configs.filter((c) => c.read).map((c) => c.url);
33002
+ return [.../* @__PURE__ */ new Set([...relays, ...readRelays])];
33003
+ } catch {
33004
+ return relays;
33005
+ }
33006
+ }
32256
33007
  function subscribeToCalendarEvents(params, onEvent, onEose) {
32257
33008
  const relays = relayManager.getRelaysForModule("calendar");
32258
33009
  const filter = {
@@ -32539,6 +33290,42 @@ async function deleteCalendarList(coordinate) {
32539
33290
  const relays = relayManager.getRelaysForModule("calendar");
32540
33291
  await nostrRuntime.publish(relays, signed);
32541
33292
  }
33293
+ async function publishParticipantRemovalEvent({
33294
+ kinds,
33295
+ eventIds = [],
33296
+ coordinates = [],
33297
+ reason = ""
33298
+ }) {
33299
+ const signer = await signerManager.getSigner();
33300
+ const tags = [];
33301
+ for (const id of eventIds) tags.push(["e", id]);
33302
+ for (const coord of coordinates) tags.push(["a", coord]);
33303
+ for (const kind of kinds) tags.push(["k", String(kind)]);
33304
+ const event = {
33305
+ kind: CALENDAR_KINDS.participantRemoval,
33306
+ created_at: Math.floor(Date.now() / 1e3),
33307
+ tags,
33308
+ content: reason
33309
+ };
33310
+ const signed = await signer.signEvent(event);
33311
+ const relays = relayManager.getRelaysForModule("calendar");
33312
+ await nostrRuntime.publish(relays, signed);
33313
+ }
33314
+ async function fetchParticipantRemovals(pubkey, relays = relayManager.getRelaysForModule("calendar")) {
33315
+ const ids = /* @__PURE__ */ new Set();
33316
+ const coordinates = /* @__PURE__ */ new Set();
33317
+ const events = await nostrRuntime.querySync(relays, {
33318
+ kinds: [CALENDAR_KINDS.participantRemoval],
33319
+ authors: [pubkey]
33320
+ });
33321
+ for (const ev of events) {
33322
+ for (const tag of ev.tags) {
33323
+ if (tag[0] === "e" && tag[1]) ids.add(tag[1]);
33324
+ else if (tag[0] === "a" && tag[1]) coordinates.add(tag[1]);
33325
+ }
33326
+ }
33327
+ return { ids, coordinates };
33328
+ }
32542
33329
  async function parseCalendarEvent(event, viewKey) {
32543
33330
  const dTag = event.tags.find((t) => t[0] === "d")?.[1] ?? "";
32544
33331
  const isPrivate = event.kind !== CALENDAR_KINDS.publicEvent;
@@ -32566,7 +33353,10 @@ async function parseCalendarEvent(event, viewKey) {
32566
33353
  const rrule = tags.find((t) => t[0] === "l" && t[2] === "rrule")?.[1] ?? (hasRruleLabel ? tags.find((t) => t[0] === "l")?.[1] : void 0) ?? tags.find((t) => t[0] === "rrule")?.[1] ?? null;
32567
33354
  const startTzid = tags.find((t) => t[0] === "start_tzid")?.[1];
32568
33355
  const endTzid = tags.find((t) => t[0] === "end_tzid")?.[1];
32569
- const registrationFormRef = tags.find((t) => t[0] === "form")?.[1];
33356
+ const formTag = tags.find((t) => t[0] === "form");
33357
+ const registrationFormRef = formTag?.[1];
33358
+ const registrationFormViewKey = formTag?.[2];
33359
+ const notificationPreference = tags.find((t) => t[0] === "notification")?.[1];
32570
33360
  return {
32571
33361
  id: dTag,
32572
33362
  eventId: event.id,
@@ -32588,13 +33378,16 @@ async function parseCalendarEvent(event, viewKey) {
32588
33378
  startTzid,
32589
33379
  endTzid,
32590
33380
  registrationFormRef,
33381
+ registrationFormViewKey,
33382
+ notificationPreference,
32591
33383
  event
32592
33384
  };
32593
33385
  }
32594
33386
  async function fetchInvitationsSync() {
32595
33387
  const signer = await signerManager.getSigner();
32596
33388
  const pubkey = await signer.getPublicKey();
32597
- const relays = relayManager.getRelaysForModule("calendar");
33389
+ const relays = await getInvitationInboxRelays(pubkey);
33390
+ const removals = await fetchParticipantRemovals(pubkey, relays);
32598
33391
  const wraps = await nostrRuntime.querySync(relays, {
32599
33392
  kinds: [CALENDAR_KINDS.giftWrap, CALENDAR_KINDS.rsvpGiftWrap],
32600
33393
  "#p": [pubkey]
@@ -32602,7 +33395,7 @@ async function fetchInvitationsSync() {
32602
33395
  const seen = /* @__PURE__ */ new Set();
32603
33396
  const out = [];
32604
33397
  for (const wrap of wraps) {
32605
- if (seen.has(wrap.id)) continue;
33398
+ if (seen.has(wrap.id) || removals.ids.has(wrap.id)) continue;
32606
33399
  seen.add(wrap.id);
32607
33400
  const invitation = await extractInvitationFromWrap(wrap);
32608
33401
  if (!invitation) continue;
@@ -32614,6 +33407,15 @@ async function fetchInvitationsSync() {
32614
33407
  }
32615
33408
  return out;
32616
33409
  }
33410
+ async function lookupEventViewKey(eventCoordinate) {
33411
+ const lists = await fetchCalendarLists();
33412
+ for (const list of lists) {
33413
+ for (const ref of list.eventRefs) {
33414
+ if (ref[0] === eventCoordinate && ref[2]) return ref[2];
33415
+ }
33416
+ }
33417
+ return void 0;
33418
+ }
32617
33419
 
32618
33420
  // ../agent/src/services/calendar/booking.ts
32619
33421
  var booking_exports = {};
@@ -32624,6 +33426,122 @@ __export(booking_exports, {
32624
33426
  fetchBookingRequests: () => fetchBookingRequests,
32625
33427
  fetchSchedulingPages: () => fetchSchedulingPages
32626
33428
  });
33429
+
33430
+ // ../agent/src/services/calendar/busyList.ts
33431
+ var MONTH_KEY_RE = /^\d{4}-\d{2}$/;
33432
+ function busyListMonthKey(value) {
33433
+ const d = typeof value === "number" ? new Date(value) : value;
33434
+ const month = String(d.getUTCMonth() + 1).padStart(2, "0");
33435
+ return `${d.getUTCFullYear()}-${month}`;
33436
+ }
33437
+ function busyListMonthKeysForRange(startMs, endMs) {
33438
+ if (!Number.isFinite(startMs) || !Number.isFinite(endMs)) return [];
33439
+ const start = Math.min(startMs, endMs);
33440
+ const end = Math.max(startMs, endMs);
33441
+ const keys = [];
33442
+ const cursor = new Date(
33443
+ Date.UTC(new Date(start).getUTCFullYear(), new Date(start).getUTCMonth(), 1)
33444
+ );
33445
+ const endMonth = new Date(
33446
+ Date.UTC(new Date(end).getUTCFullYear(), new Date(end).getUTCMonth(), 1)
33447
+ );
33448
+ while (cursor.getTime() <= endMonth.getTime()) {
33449
+ keys.push(busyListMonthKey(cursor));
33450
+ cursor.setUTCMonth(cursor.getUTCMonth() + 1);
33451
+ }
33452
+ return keys;
33453
+ }
33454
+ function busyListToTags(list) {
33455
+ const tags = [
33456
+ ["d", list.monthKey],
33457
+ ["t", list.monthKey],
33458
+ ["t", "busy"]
33459
+ ];
33460
+ for (const r of list.ranges) {
33461
+ tags.push(["block", String(Math.floor(r.start / 1e3)), String(Math.floor(r.end / 1e3))]);
33462
+ }
33463
+ return tags;
33464
+ }
33465
+ function parseBusyListEvent(event) {
33466
+ const dTag = event.tags.find((t) => t[0] === "d")?.[1] ?? "";
33467
+ if (!MONTH_KEY_RE.test(dTag)) return null;
33468
+ const ranges = [];
33469
+ for (const tag of event.tags) {
33470
+ if (tag[0] !== "block") continue;
33471
+ const start = Number(tag[1]) * 1e3;
33472
+ const end = Number(tag[2]) * 1e3;
33473
+ if (!Number.isFinite(start) || !Number.isFinite(end) || end <= start) continue;
33474
+ ranges.push({ start, end });
33475
+ }
33476
+ ranges.sort((a, b) => a.start - b.start || a.end - b.end);
33477
+ const deduped = [];
33478
+ for (const r of ranges) {
33479
+ const last = deduped[deduped.length - 1];
33480
+ if (last && last.start === r.start && last.end === r.end) continue;
33481
+ deduped.push(r);
33482
+ }
33483
+ return {
33484
+ user: event.pubkey,
33485
+ monthKey: dTag,
33486
+ ranges: deduped,
33487
+ eventId: event.id,
33488
+ createdAt: event.created_at
33489
+ };
33490
+ }
33491
+ async function fetchBusyListsForUser(pubkey, monthKeys) {
33492
+ if (monthKeys.length === 0) return [];
33493
+ const relays = relayManager.getRelaysForModule("calendar");
33494
+ const events = await nostrRuntime.querySync(relays, {
33495
+ kinds: [CALENDAR_KINDS.publicBusyList],
33496
+ authors: [pubkey],
33497
+ "#d": monthKeys
33498
+ });
33499
+ const newestPerMonth = /* @__PURE__ */ new Map();
33500
+ for (const event of events) {
33501
+ const list = parseBusyListEvent(event);
33502
+ if (!list) continue;
33503
+ const prev = newestPerMonth.get(list.monthKey);
33504
+ if (!prev || list.createdAt > prev.createdAt) newestPerMonth.set(list.monthKey, list);
33505
+ }
33506
+ return [...newestPerMonth.values()];
33507
+ }
33508
+ async function publishBusyList(list) {
33509
+ const signer = await signerManager.getSigner();
33510
+ const event = {
33511
+ kind: CALENDAR_KINDS.publicBusyList,
33512
+ created_at: Math.floor(Date.now() / 1e3),
33513
+ tags: busyListToTags(list),
33514
+ content: ""
33515
+ };
33516
+ const signed = await signer.signEvent(event);
33517
+ const relays = relayManager.getRelaysForModule("calendar");
33518
+ await nostrRuntime.publish(relays, signed);
33519
+ }
33520
+ var sameRange = (a, b) => a.start === b.start && a.end === b.end;
33521
+ async function addBusyRange(range) {
33522
+ const signer = await signerManager.getSigner();
33523
+ const pubkey = await signer.getPublicKey();
33524
+ const monthKeys = busyListMonthKeysForRange(range.start, range.end);
33525
+ const existing = new Map(
33526
+ (await fetchBusyListsForUser(pubkey, monthKeys)).map((l) => [l.monthKey, l])
33527
+ );
33528
+ for (const monthKey of monthKeys) {
33529
+ const list = existing.get(monthKey) ?? {
33530
+ user: pubkey,
33531
+ monthKey,
33532
+ ranges: [],
33533
+ eventId: "",
33534
+ createdAt: 0
33535
+ };
33536
+ if (list.ranges.some((r) => sameRange(r, range))) continue;
33537
+ const ranges = [...list.ranges, { start: range.start, end: range.end }].sort(
33538
+ (a, b) => a.start - b.start || a.end - b.end
33539
+ );
33540
+ await publishBusyList({ ...list, ranges });
33541
+ }
33542
+ }
33543
+
33544
+ // ../agent/src/services/calendar/booking.ts
32627
33545
  var BOOKING_HOST = "https://calendar.formstr.app";
32628
33546
  async function fetchSchedulingPages() {
32629
33547
  const signer = await signerManager.getSigner();
@@ -32782,6 +33700,10 @@ async function approveBookingRequest(request, calendar) {
32782
33700
  event.viewKey ?? request.viewKey ?? ""
32783
33701
  ];
32784
33702
  const updatedCalendar = await addEventToCalendarList(calendar, eventRef);
33703
+ try {
33704
+ await addBusyRange({ start: request.start, end: request.end });
33705
+ } catch {
33706
+ }
32785
33707
  await sendBookingResponse({
32786
33708
  schedulingPageRef: request.schedulingPageRef,
32787
33709
  bookerPubkey: request.bookerPubkey,
@@ -32809,7 +33731,9 @@ var service_exports3 = {};
32809
33731
  __export(service_exports3, {
32810
33732
  addSharedPage: () => addSharedPage,
32811
33733
  deletePage: () => deletePage,
33734
+ fetchAllDocMetadata: () => fetchAllDocMetadata,
32812
33735
  fetchDeletions: () => fetchDeletions2,
33736
+ fetchDocMetadata: () => fetchDocMetadata,
32813
33737
  fetchDocTags: () => fetchDocTags,
32814
33738
  fetchMyPages: () => fetchMyPages,
32815
33739
  fetchPage: () => fetchPage,
@@ -32817,17 +33741,22 @@ __export(service_exports3, {
32817
33741
  fetchSharedPages: () => fetchSharedPages,
32818
33742
  generateShareLink: () => generateShareLink,
32819
33743
  isPageDeleted: () => isPageDeleted,
33744
+ saveDocMetadata: () => saveDocMetadata,
32820
33745
  savePage: () => savePage,
32821
- saveSharedList: () => saveSharedList,
33746
+ setDocSharedAs: () => setDocSharedAs,
32822
33747
  setDocTags: () => setDocTags,
33748
+ setDocTitle: () => setDocTitle,
32823
33749
  sharePage: () => sharePage
32824
33750
  });
32825
33751
 
32826
33752
  // ../agent/src/services/pages/types.ts
32827
33753
  var PAGES_KINDS = {
32828
33754
  document: 33457,
33755
+ /** Legacy super-app shared-with-me list — read-only migration source; upstream
33756
+ * (nostr-docs) has no such kind. Shares now live in kind-34579 doc metadata. */
32829
33757
  sharedPagesList: 11234,
32830
33758
  docMetadata: 34579,
33759
+ comment: 1494,
32831
33760
  crdtOp: 22457
32832
33761
  };
32833
33762
 
@@ -32894,6 +33823,7 @@ async function fetchMyPages(viewKeys) {
32894
33823
  authors: [pubkey]
32895
33824
  });
32896
33825
  const deletions = await fetchDeletions2(relays, [pubkey]);
33826
+ const metadata = await fetchAllDocMetadata().catch(() => /* @__PURE__ */ new Map());
32897
33827
  const newest = /* @__PURE__ */ new Map();
32898
33828
  for (const ev of events) {
32899
33829
  if (isPageDeleted(ev, deletions)) continue;
@@ -32904,15 +33834,18 @@ async function fetchMyPages(viewKeys) {
32904
33834
  const summaries = [];
32905
33835
  for (const [dTag, ev] of newest) {
32906
33836
  const address = `${PAGES_KINDS.document}:${ev.pubkey}:${dTag}`;
32907
- let title = `Document ${dTag}`;
32908
- try {
32909
- title = extractTitle(await nip44SelfDecrypt(signer, ev.content));
32910
- } catch {
32911
- const vk = viewKeys?.get(address);
32912
- if (vk) {
32913
- try {
32914
- title = extractTitle(await decryptWithViewKey2(vk, ev.content));
32915
- } catch {
33837
+ const meta = metadata.get(address);
33838
+ const viewKey = meta?.viewKey ?? viewKeys?.get(address);
33839
+ let title = meta?.title || `Document ${dTag}`;
33840
+ if (!meta?.title) {
33841
+ try {
33842
+ title = extractTitle(await nip44SelfDecrypt(signer, ev.content));
33843
+ } catch {
33844
+ if (viewKey) {
33845
+ try {
33846
+ title = extractTitle(await decryptWithViewKey2(viewKey, ev.content));
33847
+ } catch {
33848
+ }
32916
33849
  }
32917
33850
  }
32918
33851
  }
@@ -32923,7 +33856,9 @@ async function fetchMyPages(viewKeys) {
32923
33856
  pubkey: ev.pubkey,
32924
33857
  createdAt: ev.created_at,
32925
33858
  isEncrypted: ev.content.length > 0,
32926
- viewKey: viewKeys?.get(address)
33859
+ viewKey,
33860
+ ...Array.isArray(meta?.tags) && meta.tags.length > 0 ? { tags: meta.tags } : {},
33861
+ ...meta?.sharedAs ? { sharedAs: meta.sharedAs } : {}
32927
33862
  });
32928
33863
  }
32929
33864
  return summaries;
@@ -32943,6 +33878,17 @@ async function fetchPage(pubkey, docId, viewKey) {
32943
33878
  decrypted = true;
32944
33879
  } catch {
32945
33880
  }
33881
+ if (!decrypted && !viewKey) {
33882
+ try {
33883
+ const meta = await fetchDocMetadata(`${PAGES_KINDS.document}:${pubkey}:${docId}`);
33884
+ if (meta?.viewKey) {
33885
+ content = await decryptWithViewKey2(meta.viewKey, event.content);
33886
+ viewKey = meta.viewKey;
33887
+ decrypted = true;
33888
+ }
33889
+ } catch {
33890
+ }
33891
+ }
32946
33892
  return {
32947
33893
  id: docId,
32948
33894
  address: `${PAGES_KINDS.document}:${event.pubkey}:${docId}`,
@@ -32957,12 +33903,24 @@ async function fetchPage(pubkey, docId, viewKey) {
32957
33903
  }
32958
33904
  async function deletePage(address) {
32959
33905
  const signer = await signerManager.getSigner();
33906
+ const [kindStr, pubkey, dTag] = address.split(":");
33907
+ let versionIds = [];
33908
+ try {
33909
+ const versions = await nostrRuntime.querySync(RELAYS(), {
33910
+ kinds: [Number(kindStr)],
33911
+ authors: [pubkey],
33912
+ "#d": [dTag]
33913
+ });
33914
+ versionIds = versions.map((v) => v.id);
33915
+ } catch {
33916
+ }
32960
33917
  const event = {
32961
33918
  kind: 5,
32962
33919
  created_at: Math.floor(Date.now() / 1e3),
32963
33920
  tags: [
32964
33921
  ["a", address],
32965
- ["k", String(PAGES_KINDS.document)]
33922
+ ["k", String(PAGES_KINDS.document)],
33923
+ ...versionIds.map((id) => ["e", id])
32966
33924
  ],
32967
33925
  content: "Deleted via Formstr"
32968
33926
  };
@@ -33003,9 +33961,16 @@ function generateShareLink(address, viewKey, editKey) {
33003
33961
  }
33004
33962
  async function sharePage(params) {
33005
33963
  const signer = await signerManager.getSigner();
33006
- const [, , dTag] = params.address.split(":");
33007
- const viewKeyHex = params.viewKey ?? generateViewKey2().hex;
33008
- const editKeyHex = params.canEdit ? params.editKey ?? generateViewKey2().hex : void 0;
33964
+ const [, ownerPubkey, dTag] = params.address.split(":");
33965
+ const originalMeta = await fetchDocMetadata(params.address).catch(() => void 0);
33966
+ if (params.canEdit && originalMeta?.sharedAs) {
33967
+ const sharedMeta = await fetchDocMetadata(originalMeta.sharedAs).catch(() => void 0);
33968
+ if (sharedMeta?.viewKey && sharedMeta.editKey) {
33969
+ return generateShareLink(originalMeta.sharedAs, sharedMeta.viewKey, sharedMeta.editKey);
33970
+ }
33971
+ }
33972
+ const viewKeyHex = params.viewKey ?? originalMeta?.viewKey ?? generateViewKey2().hex;
33973
+ const editKeyHex = params.canEdit ? params.editKey ?? originalMeta?.editKey ?? generateViewKey2().hex : void 0;
33009
33974
  const encrypted = await encryptWithViewKey2(viewKeyHex, params.content);
33010
33975
  const template = {
33011
33976
  kind: PAGES_KINDS.document,
@@ -33016,9 +33981,36 @@ async function sharePage(params) {
33016
33981
  const signed = editKeyHex ? finalizeEvent(template, hexToBytes4(editKeyHex)) : await signer.signEvent(template);
33017
33982
  await nostrRuntime.publish(RELAYS(), signed);
33018
33983
  const newAddress = `${PAGES_KINDS.document}:${signed.pubkey}:${dTag}`;
33984
+ await saveDocMetadata(newAddress, {
33985
+ viewKey: viewKeyHex,
33986
+ ...editKeyHex ? { editKey: editKeyHex } : {}
33987
+ });
33988
+ if (editKeyHex && ownerPubkey === await signer.getPublicKey()) {
33989
+ await saveDocMetadata(params.address, { sharedAs: newAddress });
33990
+ }
33019
33991
  return generateShareLink(newAddress, viewKeyHex, editKeyHex);
33020
33992
  }
33021
33993
  async function fetchSharedList() {
33994
+ const metadata = await fetchAllDocMetadata();
33995
+ const entries = [];
33996
+ for (const [address, meta] of metadata) {
33997
+ if (typeof meta.viewKey !== "string" || !meta.viewKey) continue;
33998
+ entries.push(
33999
+ typeof meta.editKey === "string" && meta.editKey ? [address, meta.viewKey, meta.editKey] : [address, meta.viewKey]
34000
+ );
34001
+ }
34002
+ for (const entry of await fetchLegacySharedList()) {
34003
+ const [address, viewKey, editKey] = entry;
34004
+ if (metadata.get(address)?.viewKey) continue;
34005
+ entries.push(entry);
34006
+ try {
34007
+ await saveDocMetadata(address, { viewKey, ...editKey ? { editKey } : {} });
34008
+ } catch {
34009
+ }
34010
+ }
34011
+ return entries;
34012
+ }
34013
+ async function fetchLegacySharedList() {
33022
34014
  const signer = await signerManager.getSigner();
33023
34015
  const pubkey = await signer.getPublicKey();
33024
34016
  const events = await nostrRuntime.querySync(RELAYS(), {
@@ -33034,30 +34026,19 @@ async function fetchSharedList() {
33034
34026
  }
33035
34027
  return [];
33036
34028
  }
33037
- async function saveSharedList(entries) {
33038
- const signer = await signerManager.getSigner();
33039
- const content = await nip44SelfEncrypt(signer, JSON.stringify(entries));
33040
- const signed = await signer.signEvent({
33041
- kind: PAGES_KINDS.sharedPagesList,
33042
- created_at: Math.floor(Date.now() / 1e3),
33043
- tags: [],
33044
- content
33045
- });
33046
- await nostrRuntime.publish(RELAYS(), signed);
33047
- }
33048
34029
  async function addSharedPage(entry) {
33049
- const existing = await fetchSharedList();
33050
- const next = existing.filter((e) => e[0] !== entry[0]);
33051
- next.push(entry);
33052
- await saveSharedList(next);
33053
- return next;
34030
+ const [address, viewKey, editKey] = entry;
34031
+ await saveDocMetadata(address, { viewKey, ...editKey ? { editKey } : {} });
33054
34032
  }
33055
34033
  async function fetchSharedPages() {
34034
+ const signer = await signerManager.getSigner();
34035
+ const userPubkey = await signer.getPublicKey();
33056
34036
  const entries = await fetchSharedList();
33057
34037
  const out = [];
33058
34038
  for (const [address, viewKey, editKey] of entries) {
33059
34039
  const [, pubkey, dTag] = address.split(":");
33060
34040
  if (!pubkey || !dTag) continue;
34041
+ if (pubkey === userPubkey) continue;
33061
34042
  const doc = await fetchPage(pubkey, dTag, viewKey);
33062
34043
  if (!doc) continue;
33063
34044
  out.push({
@@ -33074,15 +34055,19 @@ async function fetchSharedPages() {
33074
34055
  }
33075
34056
  return out;
33076
34057
  }
33077
- async function fetchDocTags(addresses) {
33078
- const result = /* @__PURE__ */ new Map();
33079
- if (addresses.length === 0) return result;
34058
+ function parseMetadataJson(json) {
34059
+ const parsed = JSON.parse(json);
34060
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
34061
+ return parsed;
34062
+ }
34063
+ return void 0;
34064
+ }
34065
+ async function fetchAllDocMetadata() {
33080
34066
  const signer = await signerManager.getSigner();
33081
34067
  const pubkey = await signer.getPublicKey();
33082
34068
  const events = await nostrRuntime.querySync(RELAYS(), {
33083
34069
  kinds: [PAGES_KINDS.docMetadata],
33084
- authors: [pubkey],
33085
- "#d": addresses
34070
+ authors: [pubkey]
33086
34071
  });
33087
34072
  const newest = /* @__PURE__ */ new Map();
33088
34073
  for (const ev of events) {
@@ -33090,18 +34075,37 @@ async function fetchDocTags(addresses) {
33090
34075
  const prev = newest.get(addr);
33091
34076
  if (!prev || ev.created_at > prev.created_at) newest.set(addr, ev);
33092
34077
  }
34078
+ const result = /* @__PURE__ */ new Map();
33093
34079
  for (const [addr, ev] of newest) {
33094
34080
  try {
33095
- const parsed = JSON.parse(await nip44SelfDecrypt(signer, ev.content));
33096
- if (Array.isArray(parsed.tags)) result.set(addr, parsed.tags);
34081
+ const meta = parseMetadataJson(await nip44SelfDecrypt(signer, ev.content));
34082
+ if (meta) result.set(addr, meta);
33097
34083
  } catch {
33098
34084
  }
33099
34085
  }
33100
34086
  return result;
33101
34087
  }
33102
- async function setDocTags(address, tags) {
34088
+ async function fetchDocMetadata(address) {
33103
34089
  const signer = await signerManager.getSigner();
33104
- const content = await nip44SelfEncrypt(signer, JSON.stringify({ tags }));
34090
+ const pubkey = await signer.getPublicKey();
34091
+ const events = await nostrRuntime.querySync(RELAYS(), {
34092
+ kinds: [PAGES_KINDS.docMetadata],
34093
+ authors: [pubkey],
34094
+ "#d": [address]
34095
+ });
34096
+ if (events.length === 0) return void 0;
34097
+ const newest = events.reduce((a, b) => b.created_at > a.created_at ? b : a);
34098
+ try {
34099
+ return parseMetadataJson(await nip44SelfDecrypt(signer, newest.content));
34100
+ } catch {
34101
+ return void 0;
34102
+ }
34103
+ }
34104
+ async function saveDocMetadata(address, patch) {
34105
+ const existing = await fetchDocMetadata(address) ?? {};
34106
+ const merged = { ...existing, ...patch };
34107
+ const signer = await signerManager.getSigner();
34108
+ const content = await nip44SelfEncrypt(signer, JSON.stringify(merged));
33105
34109
  const signed = await signer.signEvent({
33106
34110
  kind: PAGES_KINDS.docMetadata,
33107
34111
  created_at: Math.floor(Date.now() / 1e3),
@@ -33109,6 +34113,41 @@ async function setDocTags(address, tags) {
33109
34113
  content
33110
34114
  });
33111
34115
  await nostrRuntime.publish(RELAYS(), signed);
34116
+ return merged;
34117
+ }
34118
+ async function fetchDocTags(addresses) {
34119
+ const result = /* @__PURE__ */ new Map();
34120
+ if (addresses.length === 0) return result;
34121
+ const signer = await signerManager.getSigner();
34122
+ const pubkey = await signer.getPublicKey();
34123
+ const events = await nostrRuntime.querySync(RELAYS(), {
34124
+ kinds: [PAGES_KINDS.docMetadata],
34125
+ authors: [pubkey],
34126
+ "#d": addresses
34127
+ });
34128
+ const newest = /* @__PURE__ */ new Map();
34129
+ for (const ev of events) {
34130
+ const addr = ev.tags.find((t) => t[0] === "d")?.[1] ?? "";
34131
+ const prev = newest.get(addr);
34132
+ if (!prev || ev.created_at > prev.created_at) newest.set(addr, ev);
34133
+ }
34134
+ for (const [addr, ev] of newest) {
34135
+ try {
34136
+ const meta = parseMetadataJson(await nip44SelfDecrypt(signer, ev.content));
34137
+ if (meta && Array.isArray(meta.tags)) result.set(addr, meta.tags);
34138
+ } catch {
34139
+ }
34140
+ }
34141
+ return result;
34142
+ }
34143
+ async function setDocTags(address, tags) {
34144
+ await saveDocMetadata(address, { tags });
34145
+ }
34146
+ async function setDocTitle(address, title) {
34147
+ await saveDocMetadata(address, { title: title || void 0 });
34148
+ }
34149
+ async function setDocSharedAs(address, sharedAs) {
34150
+ await saveDocMetadata(address, { sharedAs });
33112
34151
  }
33113
34152
  function extractTitle(markdown) {
33114
34153
  const firstLine = markdown.trim().split("\n")[0] ?? "";
@@ -33123,11 +34162,82 @@ function randomId(length) {
33123
34162
  return result;
33124
34163
  }
33125
34164
 
34165
+ // ../agent/src/services/pages/comments.ts
34166
+ var comments_exports = {};
34167
+ __export(comments_exports, {
34168
+ fetchPageComments: () => fetchPageComments,
34169
+ parsePageComment: () => parsePageComment,
34170
+ publishPageComment: () => publishPageComment
34171
+ });
34172
+ var RELAYS2 = () => relayManager.getRelaysForModule("pages");
34173
+ async function publishPageComment(draft, viewKey, docAddress, docEventId) {
34174
+ const signer = await signerManager.getSigner();
34175
+ const innerTags = [
34176
+ ["content", draft.content],
34177
+ ["type", draft.type]
34178
+ ];
34179
+ if (draft.quote !== void 0) innerTags.push(["quote", draft.quote]);
34180
+ if (draft.context !== void 0) {
34181
+ innerTags.push(["context", draft.context.prefix, draft.context.suffix]);
34182
+ }
34183
+ const docOwnerPubkey = docAddress.split(":")[1];
34184
+ const event = {
34185
+ kind: PAGES_KINDS.comment,
34186
+ created_at: Math.floor(Date.now() / 1e3),
34187
+ content: await encryptWithViewKey2(viewKey, JSON.stringify(innerTags)),
34188
+ tags: [
34189
+ ["a", docAddress],
34190
+ ["e", docEventId],
34191
+ ["p", docOwnerPubkey]
34192
+ ]
34193
+ };
34194
+ const signed = await signer.signEvent(event);
34195
+ await nostrRuntime.publish(RELAYS2(), signed);
34196
+ return signed;
34197
+ }
34198
+ async function parsePageComment(event, viewKey) {
34199
+ try {
34200
+ const inner = JSON.parse(await decryptWithViewKey2(viewKey, event.content));
34201
+ if (!Array.isArray(inner)) return void 0;
34202
+ const rows = inner;
34203
+ const find = (name) => rows.find((r) => Array.isArray(r) && r[0] === name);
34204
+ const content = find("content")?.[1];
34205
+ if (typeof content !== "string") return void 0;
34206
+ const type = find("type")?.[1] === "suggestion" ? "suggestion" : "comment";
34207
+ const quote = find("quote")?.[1];
34208
+ const contextRow = find("context");
34209
+ return {
34210
+ id: event.id,
34211
+ author: event.pubkey,
34212
+ createdAt: event.created_at,
34213
+ content,
34214
+ type,
34215
+ ...quote !== void 0 ? { quote } : {},
34216
+ ...contextRow ? { context: { prefix: contextRow[1] ?? "", suffix: contextRow[2] ?? "" } } : {}
34217
+ };
34218
+ } catch {
34219
+ return void 0;
34220
+ }
34221
+ }
34222
+ async function fetchPageComments(docAddress, viewKey) {
34223
+ const events = await nostrRuntime.querySync(RELAYS2(), {
34224
+ kinds: [PAGES_KINDS.comment],
34225
+ "#a": [docAddress]
34226
+ });
34227
+ const out = [];
34228
+ for (const event of events) {
34229
+ const comment = await parsePageComment(event, viewKey);
34230
+ if (comment) out.push(comment);
34231
+ }
34232
+ return out.sort((a, b) => a.createdAt - b.createdAt);
34233
+ }
34234
+
33126
34235
  // ../agent/src/services/drive/service.ts
33127
34236
  var service_exports4 = {};
33128
34237
  __export(service_exports4, {
33129
34238
  deleteFile: () => deleteFile,
33130
34239
  downloadFile: () => downloadFile,
34240
+ downloadPreview: () => downloadPreview,
33131
34241
  extractFolders: () => extractFolders,
33132
34242
  fetchBlossomServers: () => fetchBlossomServers,
33133
34243
  fetchFileIndex: () => fetchFileIndex,
@@ -33143,8 +34253,8 @@ var DRIVE_KINDS = {
33143
34253
  fileMetadata: 34578
33144
34254
  };
33145
34255
  var DEFAULT_BLOSSOM_SERVERS = [
33146
- "https://blossom.primal.net",
33147
34256
  "https://nostr.download",
34257
+ "https://blossom.primal.net",
33148
34258
  "https://blossom.oxtr.dev"
33149
34259
  ];
33150
34260
 
@@ -33159,6 +34269,21 @@ async function uploadFile(params) {
33159
34269
  const authEvent = await createBlossomAuthEvent("upload", await sha256Hex2(encryptedBytes), signer);
33160
34270
  const blossom = new BlossomClient(server);
33161
34271
  const result = await blossom.upload(encryptedBytes, authEvent, params.file.type);
34272
+ let previewHash;
34273
+ if (params.preview) {
34274
+ try {
34275
+ const encryptedPreview = new TextEncoder().encode(
34276
+ await encryptFileWithExistingKey(params.preview, privateKeyHex)
34277
+ );
34278
+ const previewAuth = await createBlossomAuthEvent(
34279
+ "upload",
34280
+ await sha256Hex2(encryptedPreview),
34281
+ signer
34282
+ );
34283
+ previewHash = (await blossom.upload(encryptedPreview, previewAuth, "image/webp")).sha256;
34284
+ } catch {
34285
+ }
34286
+ }
33162
34287
  const metadata = {
33163
34288
  name: params.file.name,
33164
34289
  hash: result.sha256,
@@ -33167,11 +34292,26 @@ async function uploadFile(params) {
33167
34292
  folder: params.folder ?? "/",
33168
34293
  uploadedAt: Date.now(),
33169
34294
  server,
33170
- encryptionKey: privateKeyHex
34295
+ ...previewHash ? { previewHash } : {},
34296
+ encryptionKey: privateKeyHex,
34297
+ encryptionAlgorithm: "aes-gcm"
33171
34298
  };
33172
34299
  await saveFileMetadata(metadata);
33173
34300
  return metadata;
33174
34301
  }
34302
+ async function downloadPreview(metadata) {
34303
+ if (!metadata.previewHash) return null;
34304
+ const blossom = new BlossomClient(metadata.server);
34305
+ let authEvent;
34306
+ try {
34307
+ const signer = await signerManager.getSigner();
34308
+ authEvent = await createBlossomAuthEvent("get", metadata.previewHash, signer);
34309
+ } catch {
34310
+ }
34311
+ const encryptedBytes = await blossom.download(metadata.previewHash, authEvent);
34312
+ const ciphertext = new TextDecoder().decode(encryptedBytes);
34313
+ return decryptFileWithKey(ciphertext, metadata.encryptionKey);
34314
+ }
33175
34315
  async function downloadFile(metadata) {
33176
34316
  const blossom = new BlossomClient(metadata.server);
33177
34317
  let authEvent;
@@ -33215,7 +34355,11 @@ async function fetchFileIndex() {
33215
34355
  }
33216
34356
  async function saveFileMetadata(metadata) {
33217
34357
  const signer = await signerManager.getSigner();
33218
- const encrypted = await nip44SelfEncrypt(signer, JSON.stringify(metadata));
34358
+ const full = {
34359
+ ...metadata,
34360
+ encryptionAlgorithm: metadata.encryptionAlgorithm ?? "aes-gcm"
34361
+ };
34362
+ const encrypted = await nip44SelfEncrypt(signer, JSON.stringify(full));
33219
34363
  const event = {
33220
34364
  kind: DRIVE_KINDS.fileMetadata,
33221
34365
  created_at: Math.floor(Date.now() / 1e3),
@@ -33311,6 +34455,28 @@ __export(service_exports5, {
33311
34455
  submitPollResponse: () => submitPollResponse
33312
34456
  });
33313
34457
 
34458
+ // ../agent/src/services/polls/pow.ts
34459
+ function minePollEvent(unsigned, difficulty) {
34460
+ const event = unsigned;
34461
+ const nonceTag = ["nonce", "0", difficulty.toString()];
34462
+ event.tags.push(nonceTag);
34463
+ event.tags.push(["W", difficulty.toString()]);
34464
+ let count = 0;
34465
+ for (; ; ) {
34466
+ const now2 = Math.floor(Date.now() / 1e3);
34467
+ if (now2 !== event.created_at) {
34468
+ count = 0;
34469
+ event.created_at = now2;
34470
+ }
34471
+ nonceTag[1] = (++count).toString();
34472
+ event.id = getEventHash(event);
34473
+ if (nip13_exports.getPow(event.id) >= difficulty) return event;
34474
+ }
34475
+ }
34476
+ function hasValidPow(event, difficulty) {
34477
+ return nip13_exports.getPow(event.id) >= difficulty;
34478
+ }
34479
+
33314
34480
  // ../agent/src/services/polls/types.ts
33315
34481
  var POLLS_KINDS = {
33316
34482
  poll: 1068,
@@ -33365,7 +34531,7 @@ async function createPoll(draft) {
33365
34531
  function withModuleRelays(pollRelays) {
33366
34532
  return Array.from(/* @__PURE__ */ new Set([...pollRelays ?? [], ...relayManager.getRelaysForModule("polls")]));
33367
34533
  }
33368
- async function submitPollResponse(pollId, pollAuthor, selectedOptionIds, pollRelays) {
34534
+ async function submitPollResponse(pollId, pollAuthor, selectedOptionIds, pollRelays, powDifficulty) {
33369
34535
  const signer = await signerManager.getSigner();
33370
34536
  const relays = withModuleRelays(pollRelays);
33371
34537
  const tags = [
@@ -33375,12 +34541,22 @@ async function submitPollResponse(pollId, pollAuthor, selectedOptionIds, pollRel
33375
34541
  for (const optionId of selectedOptionIds) {
33376
34542
  tags.push(["response", optionId]);
33377
34543
  }
33378
- const event = {
34544
+ let event = {
33379
34545
  kind: POLLS_KINDS.response,
33380
34546
  created_at: Math.floor(Date.now() / 1e3),
33381
34547
  tags,
33382
34548
  content: ""
33383
34549
  };
34550
+ if (powDifficulty && powDifficulty > 0) {
34551
+ const unsigned = { ...event, pubkey: await signer.getPublicKey() };
34552
+ const mined = minePollEvent(unsigned, powDifficulty);
34553
+ event = {
34554
+ kind: mined.kind,
34555
+ created_at: mined.created_at,
34556
+ tags: mined.tags,
34557
+ content: mined.content
34558
+ };
34559
+ }
33384
34560
  const signed = await signer.signEvent(event);
33385
34561
  await nostrRuntime.publish(relays, signed);
33386
34562
  }
@@ -33428,11 +34604,12 @@ async function fetchPoll(eventId) {
33428
34604
  function resultRelays(poll) {
33429
34605
  return withModuleRelays(poll.relays);
33430
34606
  }
33431
- function latestResponsesByVoter(events, deleted) {
34607
+ function latestResponsesByVoter(events, deleted, powDifficulty = 0) {
33432
34608
  const latest = /* @__PURE__ */ new Map();
33433
34609
  for (const e of events) {
33434
34610
  if (isPollDeleted(e, deleted)) continue;
33435
34611
  if (!e.tags.some((t) => t[0] === "response")) continue;
34612
+ if (powDifficulty > 0 && !hasValidPow(e, powDifficulty)) continue;
33436
34613
  const prev = latest.get(e.pubkey);
33437
34614
  if (!prev || e.created_at > prev.created_at) latest.set(e.pubkey, e);
33438
34615
  }
@@ -33450,11 +34627,13 @@ async function fetchPollResults(poll) {
33450
34627
  const events = await nostrRuntime.querySync(relays, {
33451
34628
  kinds: [POLLS_KINDS.response, POLLS_KINDS.responseLegacy],
33452
34629
  "#e": [poll.id],
33453
- ...poll.endsAt ? { until: poll.endsAt } : {}
34630
+ ...poll.endsAt ? { until: poll.endsAt } : {},
34631
+ // PoW polls: upstream queries votes by their ["W", difficulty] tag.
34632
+ ...poll.powDifficulty ? { "#W": [String(poll.powDifficulty)] } : {}
33454
34633
  });
33455
34634
  const authors = Array.from(new Set(events.map((e) => e.pubkey)));
33456
34635
  const deleted = await fetchDeletions3(relays, authors);
33457
- return computeResults(latestResponsesByVoter(events, deleted));
34636
+ return computeResults(latestResponsesByVoter(events, deleted, poll.powDifficulty ?? 0));
33458
34637
  }
33459
34638
  async function fetchRecentPolls(limit = 20) {
33460
34639
  const relays = relayManager.getRelaysForModule("polls");
@@ -33523,7 +34702,7 @@ function computeResults(responses) {
33523
34702
  for (const [pubkey, selected] of responses) {
33524
34703
  for (const optionId of selected) {
33525
34704
  const existing = counts.get(optionId) ?? [];
33526
- existing.push(pubkey);
34705
+ if (!existing.includes(pubkey)) existing.push(pubkey);
33527
34706
  counts.set(optionId, existing);
33528
34707
  }
33529
34708
  }
@@ -33816,11 +34995,12 @@ function buildCalendarTools() {
33816
34995
  server.registerTool(
33817
34996
  "rsvp_event",
33818
34997
  {
33819
- description: "RSVP to a calendar event on your identity. Optionally suggest a new time (suggestedStart/suggestedEnd, unix seconds) and add a note (comment). Requires confirm:true.",
34998
+ description: "RSVP to a calendar event on your identity. Optionally suggest a new time (suggestedStart/suggestedEnd, unix seconds) and add a note (comment). For private events pass the event's viewKey (nsec) if known \u2014 otherwise it is looked up from your calendar lists. Requires confirm:true.",
33820
34999
  inputSchema: {
33821
35000
  eventCoordinate: external_exports.string(),
33822
35001
  status: external_exports.enum(["accepted", "declined", "tentative"]),
33823
35002
  isPrivate: external_exports.boolean().optional(),
35003
+ viewKey: external_exports.string().optional(),
33824
35004
  suggestedStart: external_exports.number().optional(),
33825
35005
  suggestedEnd: external_exports.number().optional(),
33826
35006
  comment: external_exports.string().optional(),
@@ -33831,6 +35011,7 @@ function buildCalendarTools() {
33831
35011
  eventCoordinate,
33832
35012
  status,
33833
35013
  isPrivate,
35014
+ viewKey,
33834
35015
  suggestedStart,
33835
35016
  suggestedEnd,
33836
35017
  comment,
@@ -33838,16 +35019,13 @@ function buildCalendarTools() {
33838
35019
  }) => {
33839
35020
  const blocked = requireConfirm("rsvp_event", { confirm }, `sends "${status}" RSVP`);
33840
35021
  if (blocked) return blocked;
33841
- const hasExtra = suggestedStart !== void 0 || suggestedEnd !== void 0 || comment !== void 0;
33842
- if (hasExtra) {
33843
- await rsvp_exports.rsvpToEvent(eventCoordinate, status, Boolean(isPrivate), {
33844
- suggestedStart,
33845
- suggestedEnd,
33846
- comment
33847
- });
33848
- } else {
33849
- await rsvp_exports.rsvpToEvent(eventCoordinate, status, Boolean(isPrivate));
35022
+ let key = viewKey;
35023
+ if (isPrivate && !key) {
35024
+ key = await service_exports2.lookupEventViewKey(eventCoordinate);
33850
35025
  }
35026
+ const hasExtra = suggestedStart !== void 0 || suggestedEnd !== void 0 || comment !== void 0;
35027
+ const extra = hasExtra ? { suggestedStart, suggestedEnd, comment } : void 0;
35028
+ await rsvp_exports.rsvpToEvent(eventCoordinate, status, Boolean(isPrivate), extra, key);
33851
35029
  return ok(`RSVP "${status}" sent.`);
33852
35030
  }
33853
35031
  );
@@ -33887,6 +35065,9 @@ function buildCalendarTools() {
33887
35065
  rrule: args.rrule ?? existing.repeat.rrule ?? void 0,
33888
35066
  startTzid: args.startTzid ?? existing.startTzid,
33889
35067
  registrationFormRef: existing.registrationFormRef,
35068
+ registrationFormViewKey: existing.registrationFormViewKey,
35069
+ notificationPreference: existing.notificationPreference,
35070
+ viewKey: existing.viewKey,
33890
35071
  existingId: existing.id
33891
35072
  };
33892
35073
  const event = existing.isPrivate ? await service_exports2.publishPrivateCalendarEvent(draft, existing.calendarId ?? "default") : await service_exports2.publishPublicCalendarEvent(draft);
@@ -33926,6 +35107,8 @@ function buildCalendarTools() {
33926
35107
  rrule: existing.repeat.rrule ?? void 0,
33927
35108
  startTzid: existing.startTzid,
33928
35109
  registrationFormRef: formRef,
35110
+ notificationPreference: existing.notificationPreference,
35111
+ viewKey: existing.viewKey,
33929
35112
  existingId: existing.id
33930
35113
  };
33931
35114
  const event = existing.isPrivate ? await service_exports2.publishPrivateCalendarEvent(draft, existing.calendarId ?? "default") : await service_exports2.publishPublicCalendarEvent(draft);
@@ -34208,8 +35391,13 @@ function normalizePubkeyList(values) {
34208
35391
  return values.map((v) => typeof v === "string" ? normalizePubkey(v) : null).filter((p) => !!p);
34209
35392
  }
34210
35393
  var ANSWER_TYPES = new Set(Object.values(AnswerType));
35394
+ var LEGACY_TYPE_ALIASES = {
35395
+ multiChoiceGrid: "multipleChoiceGrid" /* multipleChoiceGrid */
35396
+ };
34211
35397
  function coerceType(type) {
34212
- return type && ANSWER_TYPES.has(type) ? type : "shortText" /* shortText */;
35398
+ if (!type) return "shortText" /* shortText */;
35399
+ if (LEGACY_TYPE_ALIASES[type]) return LEGACY_TYPE_ALIASES[type];
35400
+ return ANSWER_TYPES.has(type) ? type : "shortText" /* shortText */;
34213
35401
  }
34214
35402
  function normalizeOptions(options) {
34215
35403
  if (!options) return void 0;
@@ -34229,7 +35417,8 @@ function aiFieldsToFormFields(value) {
34229
35417
  validation: f.validation,
34230
35418
  gridRows: f.gridRows,
34231
35419
  gridCols: f.gridCols,
34232
- fileConfig: f.fileConfig
35420
+ fileConfig: f.fileConfig,
35421
+ maxStars: f.maxStars
34233
35422
  }));
34234
35423
  }
34235
35424
 
@@ -34253,6 +35442,7 @@ var fieldShape = external_exports.object({
34253
35442
  }).optional(),
34254
35443
  gridRows: external_exports.array(external_exports.string()).optional(),
34255
35444
  gridCols: external_exports.array(external_exports.string()).optional(),
35445
+ maxStars: external_exports.number().optional(),
34256
35446
  fileConfig: external_exports.object({
34257
35447
  blossomServer: external_exports.string().optional(),
34258
35448
  maxBytes: external_exports.number().optional(),
@@ -34351,7 +35541,13 @@ ${form.settings.description}`);
34351
35541
  const signingKey = mine.find(
34352
35542
  (f) => f.pubkey === formAuthorPubkey && f.id === formId
34353
35543
  )?.signingKey;
34354
- const responses = await service_exports.fetchResponses(formAuthorPubkey, formId, signingKey);
35544
+ const template = await service_exports.fetchForm(formAuthorPubkey, formId);
35545
+ const responses = await service_exports.fetchResponses(
35546
+ formAuthorPubkey,
35547
+ formId,
35548
+ signingKey,
35549
+ template?.relays
35550
+ );
34355
35551
  const blocks = responses.map((r) => {
34356
35552
  const when = new Date(r.createdAt * 1e3).toISOString();
34357
35553
  const answers = r.responses.length ? r.responses.map((a) => `- ${a.fieldId}: ${a.answer}`).join("\n") : r.wasEncrypted ? "_(encrypted \u2014 you are not the form owner)_" : "_(no answers)_";
@@ -34466,24 +35662,32 @@ To let collaborators decrypt it, run \`share_form\` with their npubs.` : ""),
34466
35662
  },
34467
35663
  {
34468
35664
  name: "share_form",
34469
- description: "Share an encrypted form's view key with collaborators via NIP-59 gift-wrap so they can decrypt it. Requires confirm:true.",
35665
+ description: "Share an encrypted form's view key with collaborators via NIP-59 gift-wrap so they can decrypt it. Pubkeys in `editors` additionally receive the signing key (edit access). Requires confirm:true.",
34470
35666
  inputSchema: {
34471
35667
  formId: external_exports.string(),
34472
35668
  formPubkey: external_exports.string(),
34473
35669
  recipients: external_exports.array(external_exports.string()),
35670
+ editors: external_exports.array(external_exports.string()).optional(),
34474
35671
  confirm: external_exports.boolean().optional()
34475
35672
  },
34476
35673
  write: true,
34477
- handler: async ({ formId, formPubkey, recipients, confirm }) => {
35674
+ handler: async ({ formId, formPubkey, recipients, editors, confirm }) => {
34478
35675
  const recipientHex = normalizePubkeyList(recipients);
34479
- if (recipientHex.length === 0) return fail("No valid recipients provided.", "BAD_INPUT");
35676
+ const editorHex = normalizePubkeyList(editors ?? []);
35677
+ if (recipientHex.length === 0 && editorHex.length === 0)
35678
+ return fail("No valid recipients provided.", "BAD_INPUT");
34480
35679
  const blocked = requireConfirm(
34481
35680
  "share_form",
34482
35681
  { confirm },
34483
- `gift-wraps the view key of form ${formId} to ${recipientHex.length} recipient(s)`
35682
+ `gift-wraps the access keys of form ${formId} to ${recipientHex.length + editorHex.length} recipient(s)`
34484
35683
  );
34485
35684
  if (blocked) return blocked;
34486
- const result = await service_exports.shareForm({ formId, formPubkey, recipients: recipientHex });
35685
+ const result = await service_exports.shareForm({
35686
+ formId,
35687
+ formPubkey,
35688
+ recipients: recipientHex,
35689
+ editors: editorHex
35690
+ });
34487
35691
  const failedNote = result.failed.length ? ` (${result.failed.length} failed)` : "";
34488
35692
  return ok(`Shared view key with ${result.published} recipient(s)${failedNote}.`, {
34489
35693
  published: result.published,
@@ -34527,6 +35731,7 @@ To let collaborators decrypt it, run \`share_form\` with their npubs.` : ""),
34527
35731
  `publicly submits ${answers.length} answer(s) to form ${formId}`
34528
35732
  );
34529
35733
  if (blocked) return blocked;
35734
+ const template = await service_exports.fetchForm(formAuthorPubkey, formId);
34530
35735
  await service_exports.submitResponse(
34531
35736
  formAuthorPubkey,
34532
35737
  formId,
@@ -34535,7 +35740,9 @@ To let collaborators decrypt it, run \`share_form\` with their npubs.` : ""),
34535
35740
  answer: a.answer,
34536
35741
  metadata: a.metadata
34537
35742
  })),
34538
- Boolean(encrypt7)
35743
+ Boolean(encrypt7),
35744
+ void 0,
35745
+ template?.relays
34539
35746
  );
34540
35747
  return ok(`Submitted ${answers.length} answer(s) to form ${formId}.`);
34541
35748
  }
@@ -34590,7 +35797,7 @@ function buildPagesTools() {
34590
35797
  server.registerTool(
34591
35798
  "list_shared_pages",
34592
35799
  {
34593
- description: "List documents others have shared with you (kind-11234 inbox).",
35800
+ description: "List documents others have shared with you (kind-34579 doc-metadata entries carrying a viewKey).",
34594
35801
  inputSchema: {}
34595
35802
  },
34596
35803
  async () => {
@@ -34615,6 +35822,25 @@ function buildPagesTools() {
34615
35822
  return ok("Tags fetched.", { address, tags: map.get(address) ?? [] });
34616
35823
  }
34617
35824
  );
35825
+ server.registerTool(
35826
+ "list_page_comments",
35827
+ {
35828
+ description: "List the inline comments/suggestions on a shared document (kind 1494; needs the doc's viewKey).",
35829
+ inputSchema: { address: external_exports.string(), viewKey: external_exports.string() }
35830
+ },
35831
+ async ({ address, viewKey }) => {
35832
+ const comments = await comments_exports.fetchPageComments(address, viewKey);
35833
+ return ok(`${comments.length} comment(s).`, {
35834
+ comments: comments.map((c) => ({
35835
+ author: c.author,
35836
+ createdAt: c.createdAt,
35837
+ type: c.type,
35838
+ content: c.content,
35839
+ quote: c.quote
35840
+ }))
35841
+ });
35842
+ }
35843
+ );
34618
35844
  server.registerTool(
34619
35845
  "create_page",
34620
35846
  {
@@ -34721,6 +35947,44 @@ function buildPagesTools() {
34721
35947
  });
34722
35948
  }
34723
35949
  );
35950
+ server.registerTool(
35951
+ "add_page_comment",
35952
+ {
35953
+ description: "Add an inline comment or suggestion to a shared document (kind 1494). Requires confirm:true.",
35954
+ inputSchema: {
35955
+ address: external_exports.string(),
35956
+ eventId: external_exports.string(),
35957
+ viewKey: external_exports.string(),
35958
+ content: external_exports.string(),
35959
+ type: external_exports.enum(["comment", "suggestion"]).optional(),
35960
+ quote: external_exports.string().optional(),
35961
+ confirm: external_exports.boolean().optional()
35962
+ }
35963
+ },
35964
+ async ({
35965
+ address,
35966
+ eventId,
35967
+ viewKey,
35968
+ content,
35969
+ type,
35970
+ quote,
35971
+ confirm
35972
+ }) => {
35973
+ const blocked = requireConfirm(
35974
+ "add_page_comment",
35975
+ { confirm },
35976
+ `publishes a comment on ${address}`
35977
+ );
35978
+ if (blocked) return blocked;
35979
+ const event = await comments_exports.publishPageComment(
35980
+ { content, type: type ?? "comment", ...quote !== void 0 ? { quote } : {} },
35981
+ viewKey,
35982
+ address,
35983
+ eventId
35984
+ );
35985
+ return ok("Comment published.", { id: event.id });
35986
+ }
35987
+ );
34724
35988
  return tools;
34725
35989
  }
34726
35990
 
@@ -34857,7 +36121,13 @@ function buildPollsTools() {
34857
36121
  if (blocked) return blocked;
34858
36122
  const poll = await service_exports5.fetchPoll(pollEventId);
34859
36123
  if (!poll) return fail("Poll not found.");
34860
- await service_exports5.submitPollResponse(pollEventId, poll.pubkey, optionIds, poll.relays);
36124
+ await service_exports5.submitPollResponse(
36125
+ pollEventId,
36126
+ poll.pubkey,
36127
+ optionIds,
36128
+ poll.relays,
36129
+ poll.powDifficulty
36130
+ );
34861
36131
  return ok(`Voted on poll ${pollEventId}.`);
34862
36132
  }
34863
36133
  );
@@ -45086,7 +46356,7 @@ function adapt(r) {
45086
46356
  };
45087
46357
  }
45088
46358
  function buildServer(ctx) {
45089
- const server = new McpServer({ name: "formstr", version: "0.1.1" });
46359
+ const server = new McpServer({ name: "formstr", version: "0.1.0" });
45090
46360
  for (const t of toolRegistry) {
45091
46361
  if (t.write && !ctx.allowWrites) continue;
45092
46362
  server.registerTool(
@@ -45106,7 +46376,17 @@ async function startStdio(ctx) {
45106
46376
  // src/index.ts
45107
46377
  async function runServer(cli) {
45108
46378
  const cfg = resolveConfig(cli, process.env);
45109
- const account = await bootstrap({ account: cfg.account, relays: cfg.relays });
46379
+ const deps = process.stdin.isTTY ? {
46380
+ promptPassphrase: async (question) => {
46381
+ const io = createTerminalIo();
46382
+ try {
46383
+ return await io.promptPassphrase(question);
46384
+ } finally {
46385
+ io.close();
46386
+ }
46387
+ }
46388
+ } : {};
46389
+ const account = await bootstrap({ account: cfg.account, relays: cfg.relays }, deps);
45110
46390
  console.error(
45111
46391
  `formstr-mcp: signed in as ${account.npub} (${account.method}), writes ${cfg.allowWrites ? "ENABLED" : "disabled"}`
45112
46392
  );
@@ -45115,13 +46395,14 @@ async function runServer(cli) {
45115
46395
  }
45116
46396
  async function runLogin(cli) {
45117
46397
  const io = createTerminalIo();
46398
+ const pool = createPatchedPool();
45118
46399
  try {
45119
46400
  const account = await doLogin({
45120
46401
  signer: await buildMcpSigner(),
45121
46402
  prompt: io.prompt,
45122
46403
  promptPassphrase: io.promptPassphrase,
45123
46404
  printQr,
45124
- pool: createPatchedPool(),
46405
+ pool,
45125
46406
  relays: cli.relays
45126
46407
  });
45127
46408
  console.error(
@@ -45129,6 +46410,10 @@ async function runLogin(cli) {
45129
46410
  );
45130
46411
  } finally {
45131
46412
  io.close();
46413
+ try {
46414
+ pool.destroy();
46415
+ } catch {
46416
+ }
45132
46417
  }
45133
46418
  }
45134
46419
  async function main() {
@@ -45136,18 +46421,18 @@ async function main() {
45136
46421
  switch (cli.command) {
45137
46422
  case "login":
45138
46423
  await runLogin(cli);
45139
- return;
46424
+ return cli.command;
45140
46425
  case "logout": {
45141
46426
  await doLogout(await buildMcpSigner(), cli.account);
45142
46427
  console.error("formstr-mcp: signed out.");
45143
- return;
46428
+ return cli.command;
45144
46429
  }
45145
46430
  case "whoami": {
45146
46431
  const who = whoami(await buildMcpSigner());
45147
46432
  console.error(
45148
46433
  who ? `formstr-mcp: ${who.npub} (${who.method})` : "formstr-mcp: not signed in."
45149
46434
  );
45150
- return;
46435
+ return cli.command;
45151
46436
  }
45152
46437
  case "accounts": {
45153
46438
  const signer = await buildMcpSigner();
@@ -45155,26 +46440,30 @@ async function main() {
45155
46440
  const accounts = listAccounts(signer);
45156
46441
  if (accounts.length === 0) {
45157
46442
  console.error("formstr-mcp: no accounts. Run `formstr-mcp login`.");
45158
- return;
46443
+ return cli.command;
45159
46444
  }
45160
46445
  for (const a of accounts) {
45161
46446
  const marker = active && a.pubkey === active.pubkey ? "* " : " ";
45162
46447
  console.error(`${marker}${a.npub} (${a.method})`);
45163
46448
  }
45164
- return;
46449
+ return cli.command;
45165
46450
  }
45166
46451
  case "run":
45167
46452
  default:
45168
46453
  await runServer(cli);
46454
+ return "run";
45169
46455
  }
45170
46456
  }
45171
- main().catch((err) => {
46457
+ main().then((command) => {
46458
+ if (command !== "run") process.exit(0);
46459
+ }).catch((err) => {
45172
46460
  console.error("formstr-mcp: fatal:", err instanceof Error ? err.message : err);
45173
46461
  process.exit(1);
45174
46462
  });
45175
46463
  /*! Bundled license information:
45176
46464
 
45177
46465
  @noble/hashes/utils.js:
46466
+ @noble/hashes/esm/utils.js:
45178
46467
  (*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
45179
46468
 
45180
46469
  @noble/curves/utils.js: