@formstr/mcp 0.1.1 → 0.3.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
@@ -24289,7 +24405,26 @@ async function loginImport(deps) {
24289
24405
  }
24290
24406
  async function loginBunker(deps) {
24291
24407
  const uri = (await deps.prompt("Paste the bunker:// URI: ")).trim();
24292
- await deps.signer.loginWithBunkerUri(uri, { pool: deps.pool, perms: NIP46_PERMS });
24408
+ try {
24409
+ await deps.signer.loginWithBunkerUri(uri, { pool: deps.pool, perms: NIP46_PERMS });
24410
+ } catch (err) {
24411
+ throw new Error(describeBunkerError(uri, err));
24412
+ }
24413
+ }
24414
+ function describeBunkerError(uri, err) {
24415
+ const raw = err instanceof Error ? err.message : String(err);
24416
+ const hasSecret = /[?&]secret=/.test(uri);
24417
+ const expected = "`bunker://<pubkey>?relay=wss://\u2026&secret=<token>`";
24418
+ if (/no secret/i.test(raw) || !hasSecret) {
24419
+ return "Could not pair with this bunker URI" + (hasSecret ? "" : " \u2014 it has no `secret=` token") + `, so the remote signer rejected the connection. A pairing URI must look like ${expected}. Copy the FULL connection URI (including \`&secret=\u2026\`) from your signer app (e.g. nsec.app \u2192 Connect app), and make sure nothing was truncated \u2014 the secret is often one-time, so generate a fresh connection if needed. Or use the QR flow: \`formstr-mcp login\` \u2192 \`q\`. (signer reported: ${raw})`;
24420
+ }
24421
+ if (/invalid bunker uri/i.test(raw)) {
24422
+ return `That doesn't look like a valid bunker URI. Expected ${expected}. (parser reported: ${raw})`;
24423
+ }
24424
+ if (/at least one relay/i.test(raw)) {
24425
+ return `This bunker URI has no relays. Expected ${expected}. (reported: ${raw})`;
24426
+ }
24427
+ return `Bunker login failed: ${raw}. Check that the URI includes a \`secret=\` token and at least one \`relay=\`, that the remote signer is online, or try the QR flow (\`formstr-mcp login\` \u2192 \`q\`).`;
24293
24428
  }
24294
24429
  async function loginNostrConnect(deps, log) {
24295
24430
  const relays = deps.relays?.length ? deps.relays : DEFAULT_NOSTRCONNECT_RELAYS;
@@ -24310,6 +24445,19 @@ function whoami(signer) {
24310
24445
  function listAccounts(signer) {
24311
24446
  return signer.listAccounts();
24312
24447
  }
24448
+ function findAccount(accounts, target) {
24449
+ return accounts.find((a) => a.npub === target || a.pubkey === target) ?? null;
24450
+ }
24451
+ async function doSwitch(signer, target) {
24452
+ const match = findAccount(listAccounts(signer), target);
24453
+ if (!match) {
24454
+ throw new Error(
24455
+ `No stored account matching "${target}". Run \`formstr-mcp accounts\` to list them.`
24456
+ );
24457
+ }
24458
+ await signer.switchAccount(match.pubkey);
24459
+ return match;
24460
+ }
24313
24461
 
24314
24462
  // src/auth/kvStore.ts
24315
24463
  var import_node_crypto = require("crypto");
@@ -25400,25 +25548,39 @@ function createPatchedPool() {
25400
25548
 
25401
25549
  // src/auth/terminal.ts
25402
25550
  var readline = __toESM(require("readline"));
25551
+ var import_node_stream = require("stream");
25403
25552
  var import_qrcode = __toESM(require_lib());
25404
25553
  function createTerminalIo() {
25405
- const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
25554
+ const stderr = process.stderr;
25555
+ let muted = false;
25556
+ const output = new import_node_stream.Writable({
25557
+ write(chunk, _enc, cb) {
25558
+ if (!muted) stderr.write(chunk);
25559
+ cb();
25560
+ }
25561
+ });
25562
+ Object.defineProperties(output, {
25563
+ isTTY: { get: () => stderr.isTTY },
25564
+ columns: { get: () => stderr.columns },
25565
+ rows: { get: () => stderr.rows }
25566
+ });
25567
+ const rl = readline.createInterface({
25568
+ input: process.stdin,
25569
+ output,
25570
+ terminal: Boolean(process.stdin.isTTY)
25571
+ });
25406
25572
  return {
25407
25573
  prompt(question) {
25408
25574
  return new Promise((resolve) => rl.question(question, resolve));
25409
25575
  },
25410
25576
  promptPassphrase(question) {
25411
25577
  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");
25578
+ rl.question(question, (answer) => {
25579
+ muted = false;
25580
+ stderr.write("\n");
25420
25581
  resolve(answer);
25421
25582
  });
25583
+ muted = true;
25422
25584
  });
25423
25585
  },
25424
25586
  close() {
@@ -26027,15 +26189,29 @@ var EventStore = class {
26027
26189
  eventsByDTag = /* @__PURE__ */ new Map();
26028
26190
  // "kind:pubkey:dtag" → event
26029
26191
  deletedIds = /* @__PURE__ */ new Set();
26192
+ // NIP-09 coordinate tombstones: "kind:pubkey:dtag" → newest deletion created_at.
26193
+ // An addressable event is only rejected when its created_at ≤ that time, so a
26194
+ // legitimate re-publish after a delete survives.
26195
+ deletedCoordinates = /* @__PURE__ */ new Map();
26196
+ // Kind-84 participant-removal tombstones.
26197
+ ignoredEventIds = /* @__PURE__ */ new Set();
26198
+ ignoredCoordinates = /* @__PURE__ */ new Set();
26030
26199
  subscriptions = [];
26031
26200
  /** Store event. Returns false if duplicate or older replaceable. */
26032
26201
  store(event) {
26033
26202
  if (this.deletedIds.has(event.id))
26034
26203
  return false;
26204
+ if (this.ignoredEventIds.has(event.id))
26205
+ return false;
26035
26206
  if (this.eventsById.has(event.id))
26036
26207
  return false;
26037
26208
  if (this.isReplaceable(event.kind) || this.isParameterizedReplaceable(event.kind)) {
26038
26209
  const addr = this.getAddress(event);
26210
+ const deletedAt = this.deletedCoordinates.get(addr);
26211
+ if (deletedAt !== void 0 && event.created_at <= deletedAt)
26212
+ return false;
26213
+ if (this.ignoredCoordinates.has(addr))
26214
+ return false;
26039
26215
  const existing = this.eventsByDTag.get(addr);
26040
26216
  if (existing && existing.created_at >= event.created_at) {
26041
26217
  return false;
@@ -26050,6 +26226,9 @@ var EventStore = class {
26050
26226
  if (event.kind === 5) {
26051
26227
  this.handleDeletion(event);
26052
26228
  }
26229
+ if (event.kind === 84) {
26230
+ this.handleParticipantRemoval(event);
26231
+ }
26053
26232
  this.eventsById.set(event.id, event);
26054
26233
  if (!this.eventsByKind.has(event.kind)) {
26055
26234
  this.eventsByKind.set(event.kind, /* @__PURE__ */ new Map());
@@ -26118,14 +26297,50 @@ var EventStore = class {
26118
26297
  if (tag[0] === "e" && tag[1]) {
26119
26298
  const targetId = tag[1];
26120
26299
  const target = this.eventsById.get(targetId);
26121
- if (target && target.pubkey === deletionEvent.pubkey) {
26122
- this.remove(targetId);
26123
- }
26300
+ if (target && target.pubkey !== deletionEvent.pubkey)
26301
+ continue;
26124
26302
  this.deletedIds.add(targetId);
26303
+ if (target)
26304
+ this.remove(targetId);
26125
26305
  }
26126
26306
  if (tag[0] === "a" && tag[1]) {
26307
+ const coordAuthor = tag[1].split(":")[1];
26308
+ if (coordAuthor && coordAuthor !== deletionEvent.pubkey)
26309
+ continue;
26310
+ const prev = this.deletedCoordinates.get(tag[1]) ?? 0;
26311
+ if (deletionEvent.created_at > prev) {
26312
+ this.deletedCoordinates.set(tag[1], deletionEvent.created_at);
26313
+ }
26127
26314
  const existing = this.eventsByDTag.get(tag[1]);
26128
- if (existing && existing.pubkey === deletionEvent.pubkey) {
26315
+ if (existing && existing.created_at <= deletionEvent.created_at) {
26316
+ this.remove(existing.id);
26317
+ this.eventsByDTag.delete(tag[1]);
26318
+ }
26319
+ }
26320
+ }
26321
+ }
26322
+ /**
26323
+ * Process a kind-84 participant removal (a participant opting out of an
26324
+ * event). Unlike NIP-09 there is no same-author rule — any participant
26325
+ * (`p` tag on the target) may remove it for themselves; targets that aren't
26326
+ * cached yet are tombstoned unconditionally, matching the standalone
26327
+ * calendar's EventStore.
26328
+ */
26329
+ handleParticipantRemoval(removalEvent) {
26330
+ const isParticipant = (target) => target.tags.some((t) => t[0] === "p" && t[1] === removalEvent.pubkey);
26331
+ for (const tag of removalEvent.tags) {
26332
+ if (tag[0] === "e" && tag[1]) {
26333
+ const existing = this.eventsById.get(tag[1]);
26334
+ if (existing) {
26335
+ if (!isParticipant(existing))
26336
+ continue;
26337
+ this.remove(tag[1]);
26338
+ }
26339
+ this.ignoredEventIds.add(tag[1]);
26340
+ } else if (tag[0] === "a" && tag[1]) {
26341
+ this.ignoredCoordinates.add(tag[1]);
26342
+ const existing = this.eventsByDTag.get(tag[1]);
26343
+ if (existing && isParticipant(existing)) {
26129
26344
  this.remove(existing.id);
26130
26345
  this.eventsByDTag.delete(tag[1]);
26131
26346
  }
@@ -26149,6 +26364,9 @@ var EventStore = class {
26149
26364
  this.eventsByAuthor.clear();
26150
26365
  this.eventsByDTag.clear();
26151
26366
  this.deletedIds.clear();
26367
+ this.deletedCoordinates.clear();
26368
+ this.ignoredEventIds.clear();
26369
+ this.ignoredCoordinates.clear();
26152
26370
  this.subscriptions = [];
26153
26371
  }
26154
26372
  get size() {
@@ -26385,9 +26603,20 @@ var NostrRuntime = class {
26385
26603
  }
26386
26604
  });
26387
26605
  }
26388
- /** Publish event to relays */
26389
- async publish(relays, event) {
26390
- await Promise.allSettled(this.pool.publish(relays, event));
26606
+ /**
26607
+ * Publish event to relays. Bounded by `timeoutMs`: a relay that never acks must
26608
+ * not hang the caller (e.g. a form submit) forever — best-effort fan-out.
26609
+ */
26610
+ async publish(relays, event, timeoutMs = 1e4) {
26611
+ let timer;
26612
+ const timeout = new Promise((resolve) => {
26613
+ timer = setTimeout(resolve, timeoutMs);
26614
+ });
26615
+ try {
26616
+ await Promise.race([Promise.allSettled(this.pool.publish(relays, event)), timeout]);
26617
+ } finally {
26618
+ clearTimeout(timer);
26619
+ }
26391
26620
  this.eventStore.store(event);
26392
26621
  }
26393
26622
  /** Close all subscriptions + clear caches. Use in tests; rarely in production. */
@@ -26451,7 +26680,7 @@ var MODULE_DEFAULT_RELAYS = {
26451
26680
  "wss://relay.primal.net",
26452
26681
  "wss://nos.lol",
26453
26682
  "wss://relay.nostr.wirednet.jp",
26454
- "wss://relay.yakinonne.com",
26683
+ "wss://nostr-01.yakihonne.com",
26455
26684
  "wss://relay.snort.social",
26456
26685
  "wss://relay.nostr.band",
26457
26686
  "wss://nostr21.com"
@@ -26683,7 +26912,7 @@ var BlossomClient = class {
26683
26912
  */
26684
26913
  async upload(data, authEvent, contentType) {
26685
26914
  const authHeader = `Nostr ${btoa(JSON.stringify(authEvent))}`;
26686
- const sha2562 = await sha256Hex(data);
26915
+ const sha2564 = await sha256Hex(data);
26687
26916
  const response = await fetch(`${this.serverUrl}/upload`, {
26688
26917
  method: "PUT",
26689
26918
  headers: {
@@ -26691,7 +26920,7 @@ var BlossomClient = class {
26691
26920
  "Content-Type": contentType ?? "application/octet-stream",
26692
26921
  // BUD-02: lets servers verify the blob and is required by some (e.g.
26693
26922
  // the standalone formstr-drive's default servers).
26694
- "X-SHA-256": sha2562
26923
+ "X-SHA-256": sha2564
26695
26924
  },
26696
26925
  body: data
26697
26926
  });
@@ -26701,26 +26930,26 @@ var BlossomClient = class {
26701
26930
  const text = await response.text();
26702
26931
  try {
26703
26932
  const json = JSON.parse(text);
26704
- const hash = json.sha256 ?? json.x ?? sha2562;
26933
+ const hash = json.sha256 ?? json.x ?? sha2564;
26705
26934
  return {
26706
26935
  sha256: hash,
26707
26936
  url: json.url ?? `${this.serverUrl}/${hash}`,
26708
26937
  size: json.size ?? data.byteLength
26709
26938
  };
26710
26939
  } catch {
26711
- const hash = text.trim() || sha2562;
26940
+ const hash = text.trim() || sha2564;
26712
26941
  return { sha256: hash, url: `${this.serverUrl}/${hash}`, size: data.byteLength };
26713
26942
  }
26714
26943
  }
26715
26944
  /**
26716
26945
  * BUD-03: Download file blob by SHA-256 hash.
26717
26946
  */
26718
- async download(sha2562, authEvent) {
26947
+ async download(sha2564, authEvent) {
26719
26948
  const headers = {};
26720
26949
  if (authEvent) {
26721
26950
  headers["Authorization"] = `Nostr ${btoa(JSON.stringify(authEvent))}`;
26722
26951
  }
26723
- const response = await fetch(`${this.serverUrl}/${sha2562}`, { headers });
26952
+ const response = await fetch(`${this.serverUrl}/${sha2564}`, { headers });
26724
26953
  if (!response.ok) {
26725
26954
  throw new Error(`Blossom download failed: ${response.status} ${response.statusText}`);
26726
26955
  }
@@ -26730,9 +26959,9 @@ var BlossomClient = class {
26730
26959
  /**
26731
26960
  * BUD-04: Delete file blob by SHA-256 hash.
26732
26961
  */
26733
- async delete(sha2562, authEvent) {
26962
+ async delete(sha2564, authEvent) {
26734
26963
  const authHeader = `Nostr ${btoa(JSON.stringify(authEvent))}`;
26735
- const response = await fetch(`${this.serverUrl}/${sha2562}`, {
26964
+ const response = await fetch(`${this.serverUrl}/${sha2564}`, {
26736
26965
  method: "DELETE",
26737
26966
  headers: { Authorization: authHeader }
26738
26967
  });
@@ -26751,18 +26980,18 @@ async function sha256Hex(data) {
26751
26980
  }
26752
26981
 
26753
26982
  // ../core/dist/blossom/auth.js
26754
- async function createBlossomAuthEvent(operation, sha2562, signer) {
26983
+ async function createBlossomAuthEvent(operation, sha2564, signer) {
26755
26984
  const now2 = Math.floor(Date.now() / 1e3);
26756
26985
  const event = {
26757
26986
  kind: 24242,
26758
26987
  created_at: now2,
26759
26988
  tags: [
26760
26989
  ["t", operation],
26761
- ["x", sha2562],
26990
+ ["x", sha2564],
26762
26991
  ["expiration", String(now2 + 300)]
26763
26992
  // 5 min validity
26764
26993
  ],
26765
- content: `Authorize ${operation} for ${sha2562}`
26994
+ content: `Authorize ${operation} for ${sha2564}`
26766
26995
  };
26767
26996
  return signer.signEvent(event);
26768
26997
  }
@@ -26837,17 +27066,6 @@ async function wrapEvent3(event, signer, recipientPubkey, wrapKind = 1059) {
26837
27066
  const seal = await createSeal2(rumor, signer, recipientPubkey);
26838
27067
  return createWrap2(seal, recipientPubkey, wrapKind);
26839
27068
  }
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
27069
  async function unwrapEvent3(wrappedEvent, signer) {
26852
27070
  if (!signer.nip44Decrypt) {
26853
27071
  throw new Error("Signer does not support NIP-44 for unwrap");
@@ -26865,21 +27083,34 @@ function randomizeTimestamp(timestamp) {
26865
27083
 
26866
27084
  // ../core/dist/crypto/nkeys.js
26867
27085
  var PREFIX = "nkeys";
27086
+ var BECH32_LIMIT = 2048;
26868
27087
  function encodeNKeys(keys) {
26869
- const tlvBytes = [];
27088
+ const encoder = new TextEncoder();
27089
+ const names = [];
27090
+ const values = [];
26870
27091
  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);
27092
+ const nameBytes = encoder.encode(name);
27093
+ const valueBytes = encoder.encode(value);
27094
+ if (nameBytes.length > 255)
27095
+ throw new Error(`nkeys: key name too long (${name.length})`);
27096
+ if (valueBytes.length > 255)
27097
+ throw new Error(`nkeys: value too long for key "${name}"`);
27098
+ names.push(nameBytes);
27099
+ values.push(valueBytes);
27100
+ }
27101
+ const tlvBytes = [];
27102
+ for (const nameBytes of names) {
27103
+ tlvBytes.push(0, nameBytes.length, ...nameBytes);
27104
+ }
27105
+ for (const valueBytes of values) {
27106
+ tlvBytes.push(1, valueBytes.length, ...valueBytes);
27107
+ }
27108
+ const words = bech32ToWords(new Uint8Array(tlvBytes));
27109
+ const encoded = bech32Encode(PREFIX, words);
27110
+ if (encoded.length > BECH32_LIMIT) {
27111
+ throw new Error(`nkeys: encoded length ${encoded.length} exceeds ${BECH32_LIMIT}`);
27112
+ }
27113
+ return encoded;
26883
27114
  }
26884
27115
  var CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
26885
27116
  function bech32Encode(prefix, words) {
@@ -26991,11 +27222,16 @@ async function aesGcmDecrypt(ciphertext, conversationKey) {
26991
27222
  }
26992
27223
  async function encryptFileWithKey(fileBytes) {
26993
27224
  const secretKey = generateSecretKey();
27225
+ const privateKeyHex = bytesToHex(secretKey);
27226
+ const ciphertext = await encryptFileWithExistingKey(fileBytes, privateKeyHex);
27227
+ return { ciphertext, privateKeyHex };
27228
+ }
27229
+ async function encryptFileWithExistingKey(fileBytes, privateKeyHex) {
27230
+ const secretKey = hexToBytes(privateKeyHex);
26994
27231
  const pubkey = getPublicKey(secretKey);
26995
27232
  const conversationKey = nip44_exports.v2.utils.getConversationKey(secretKey, pubkey);
26996
27233
  const plaintextBase64 = uint8ArrayToBase64(fileBytes);
26997
- const ciphertext = await aesGcmEncrypt(plaintextBase64, conversationKey);
26998
- return { ciphertext, privateKeyHex: bytesToHex(secretKey) };
27234
+ return aesGcmEncrypt(plaintextBase64, conversationKey);
26999
27235
  }
27000
27236
  async function decryptFileWithKey(ciphertext, privateKeyHex) {
27001
27237
  const secretKey = hexToBytes(privateKeyHex);
@@ -27021,23 +27257,23 @@ var KIND_MODULE_MAP = {
27021
27257
  30168: "forms",
27022
27258
  // Form template
27023
27259
  // Calendar
27024
- 31922: "calendar",
27025
- // Date-based calendar event
27026
27260
  31923: "calendar",
27027
- // Time-based calendar event
27028
- 31924: "calendar",
27261
+ // Public time-based event (NIP-52)
27262
+ 32678: "calendar",
27263
+ // Private event
27264
+ 32679: "calendar",
27265
+ // Private event (legacy recurring variant, read-only)
27266
+ 32123: "calendar",
27029
27267
  // Calendar list
27030
27268
  // Pages
27031
- 30023: "pages",
27032
- // Long-form content (NIP-23)
27033
- 30024: "pages",
27034
- // Draft article
27269
+ 33457: "pages",
27270
+ // Encrypted markdown doc (nostr-docs)
27035
27271
  // Drive
27036
- 30563: "drive",
27037
- // Drive file tree
27272
+ 34578: "drive",
27273
+ // File metadata (formstr-drive)
27038
27274
  // Polls
27039
27275
  1068: "polls"
27040
- // Poll event
27276
+ // Poll event (NIP-88)
27041
27277
  };
27042
27278
  function createRef(_module, kind, pubkey, identifier, relays = []) {
27043
27279
  return nip19_exports.naddrEncode({ kind, pubkey, identifier, relays });
@@ -27162,7 +27398,7 @@ async function bootstrap(input, deps = {}) {
27162
27398
  }
27163
27399
  async function selectAccount(signer, requested) {
27164
27400
  if (requested) {
27165
- const match = signer.listAccounts().find((a) => a.pubkey === requested);
27401
+ const match = findAccount(signer.listAccounts(), requested);
27166
27402
  if (!match) {
27167
27403
  throw new Error(
27168
27404
  `No stored account for --account ${requested}. Run \`formstr-mcp accounts\` to list them.`
@@ -27179,18 +27415,7 @@ async function selectAccount(signer, requested) {
27179
27415
  }
27180
27416
  async function unlock(signer, account, deps) {
27181
27417
  if (account.method === "ncryptsec") {
27182
- if (!account.ncryptsec) {
27183
- throw new Error(
27184
- "Stored ncryptsec account is missing its encrypted key. Run `formstr-mcp login`."
27185
- );
27186
- }
27187
- const passphrase = deps.passphrase ?? process.env.FORMSTR_MCP_NCRYPTSEC_PASSPHRASE;
27188
- if (!passphrase) {
27189
- throw new Error(
27190
- "Set FORMSTR_MCP_NCRYPTSEC_PASSPHRASE to unlock the ncryptsec account at boot."
27191
- );
27192
- }
27193
- await signer.loginWithNcryptsec(account.ncryptsec, passphrase);
27418
+ await unlockNcryptsec(signer, account, deps);
27194
27419
  return;
27195
27420
  }
27196
27421
  if (account.method === "nip46") {
@@ -27198,41 +27423,120 @@ async function unlock(signer, account, deps) {
27198
27423
  throw new Error("Stored nip46 account is missing its session. Run `formstr-mcp login`.");
27199
27424
  }
27200
27425
  const pool = deps.pool ?? createPatchedPool();
27201
- await signer.loginWithBunkerUri(account.nip46.uri, {
27202
- clientSecretKey: hexToBytes2(account.nip46.clientSecretKey),
27203
- pool
27204
- });
27426
+ const active = await signer.unlock({ pool });
27427
+ if (!active) {
27428
+ throw new Error(
27429
+ "Could not resume the stored nip46 session (its keys are incomplete). Run `formstr-mcp login` to re-pair."
27430
+ );
27431
+ }
27205
27432
  return;
27206
27433
  }
27207
27434
  throw new Error(
27208
27435
  `Account method "${account.method}" cannot be unlocked headlessly \u2014 use ncryptsec or nip46.`
27209
27436
  );
27210
27437
  }
27438
+ var MAX_PASSPHRASE_ATTEMPTS = 3;
27439
+ async function unlockNcryptsec(signer, account, deps) {
27440
+ if (!account.ncryptsec) {
27441
+ throw new Error(
27442
+ `Account ${account.npub} is stored as an ncryptsec but its encrypted key is missing. Re-add it with \`formstr-mcp login\`.`
27443
+ );
27444
+ }
27445
+ const ncryptsec = account.ncryptsec;
27446
+ const tryUnlock = async (passphrase) => {
27447
+ try {
27448
+ await signer.loginWithNcryptsec(ncryptsec, passphrase);
27449
+ return true;
27450
+ } catch {
27451
+ return false;
27452
+ }
27453
+ };
27454
+ const configured = deps.passphrase ?? process.env.FORMSTR_MCP_NCRYPTSEC_PASSPHRASE;
27455
+ if (configured) {
27456
+ if (await tryUnlock(configured)) return;
27457
+ if (!deps.promptPassphrase) {
27458
+ throw new Error(
27459
+ `The configured passphrase did not unlock account ${account.npub} \u2014 it is most likely the wrong passphrase for this account (each account has its own). Set FORMSTR_MCP_NCRYPTSEC_PASSPHRASE to the passphrase that matches this account, or switch to another with \`formstr-mcp switch <npub>\` (a nip46/bunker account needs no passphrase).`
27460
+ );
27461
+ }
27462
+ console.error(
27463
+ `formstr-mcp: the configured passphrase didn't unlock ${account.npub} \u2014 enter it manually.`
27464
+ );
27465
+ } else if (!deps.promptPassphrase) {
27466
+ throw new Error(
27467
+ `Cannot unlock account ${account.npub} (ncryptsec): no passphrase is available. FORMSTR_MCP_NCRYPTSEC_PASSPHRASE is not set and this process has no interactive terminal to prompt on \u2014 an MCP host runs the server with stdin wired to the JSON-RPC channel. Fix: add FORMSTR_MCP_NCRYPTSEC_PASSPHRASE to your MCP server config's \`env\`, or switch to a nip46/bunker account with \`formstr-mcp switch <npub>\` (resumes with no passphrase).`
27468
+ );
27469
+ }
27470
+ const promptPassphrase = deps.promptPassphrase;
27471
+ for (let attempt = 1; attempt <= MAX_PASSPHRASE_ATTEMPTS; attempt++) {
27472
+ const passphrase = await promptPassphrase(`Passphrase to unlock ${account.npub}: `);
27473
+ if (await tryUnlock(passphrase)) return;
27474
+ const left = MAX_PASSPHRASE_ATTEMPTS - attempt;
27475
+ if (left > 0) console.error(`formstr-mcp: incorrect passphrase, ${left} attempt(s) left.`);
27476
+ }
27477
+ throw new Error(
27478
+ `Incorrect passphrase for account ${account.npub} after ${MAX_PASSPHRASE_ATTEMPTS} attempts.`
27479
+ );
27480
+ }
27211
27481
 
27212
27482
  // src/cli.ts
27213
- var SUBCOMMANDS = /* @__PURE__ */ new Set(["login", "logout", "whoami", "accounts"]);
27483
+ var SUBCOMMANDS = /* @__PURE__ */ new Set(["login", "logout", "whoami", "accounts", "switch", "help"]);
27214
27484
  function parseCli(argv) {
27215
27485
  const rest = [...argv];
27486
+ if (rest.includes("-h") || rest.includes("--help")) {
27487
+ return { command: "help", allowWrites: false };
27488
+ }
27216
27489
  let command = "run";
27217
27490
  if (rest[0] && SUBCOMMANDS.has(rest[0])) {
27218
27491
  command = rest.shift();
27219
27492
  }
27220
27493
  let relays;
27221
27494
  let account;
27495
+ let target;
27222
27496
  let allowWrites = false;
27223
27497
  for (let i4 = 0; i4 < rest.length; i4++) {
27224
27498
  const arg = rest[i4];
27225
27499
  if (arg === "--relays") relays = splitRelays(rest[++i4]);
27226
27500
  else if (arg === "--allow-writes") allowWrites = true;
27227
27501
  else if (arg === "--account") account = rest[++i4];
27502
+ else if (!arg.startsWith("-") && target === void 0) target = arg;
27228
27503
  }
27229
- return { command, relays, allowWrites, account };
27504
+ return { command, relays, allowWrites, account, target };
27505
+ }
27506
+ function formatFatal(err, debug = false) {
27507
+ if (err instanceof Error) return debug && err.stack ? err.stack : err.message;
27508
+ return String(err);
27230
27509
  }
27231
27510
  function splitRelays(value) {
27232
27511
  if (!value) return void 0;
27233
27512
  const parts = value.split(",").map((s) => s.trim()).filter(Boolean);
27234
27513
  return parts.length ? parts : void 0;
27235
27514
  }
27515
+ function helpText() {
27516
+ return [
27517
+ "formstr-mcp \u2014 MCP server for the Formstr super-app (Nostr forms & more).",
27518
+ "",
27519
+ "Usage: formstr-mcp [command] [flags]",
27520
+ "",
27521
+ "Commands:",
27522
+ " run Run the stdio MCP server (default when no command is given).",
27523
+ " login Sign in (create / import / bunker URI / QR) and store the key.",
27524
+ " logout [npub] Remove a stored account (defaults to the active one).",
27525
+ " whoami Print the active account.",
27526
+ " accounts List stored accounts ('*' marks the active one).",
27527
+ " switch <npub> Set the active account (accepts an npub or hex pubkey).",
27528
+ " help Show this help (also -h, --help).",
27529
+ "",
27530
+ "Flags:",
27531
+ " --allow-writes Enable gated write tools (update / delete / share / submit).",
27532
+ " --account <npub|hex> Boot a specific account instead of the active one.",
27533
+ " --relays <a,b,\u2026> Override relays (comma-separated).",
27534
+ "",
27535
+ "Env:",
27536
+ " FORMSTR_MCP_NCRYPTSEC_PASSPHRASE Unlock the active ncryptsec account at boot.",
27537
+ " FORMSTR_MCP_PASSPHRASE Encrypt the keystore file (keychain-less hosts)."
27538
+ ].join("\n");
27539
+ }
27236
27540
 
27237
27541
  // src/config.ts
27238
27542
  function resolveConfig(cli, env) {
@@ -31315,6 +31619,7 @@ __export(service_exports, {
31315
31619
  createForm: () => createForm,
31316
31620
  deleteForm: () => deleteForm,
31317
31621
  fetchForm: () => fetchForm,
31622
+ fetchFormKeys: () => fetchFormKeys,
31318
31623
  fetchFormSummaryFromRef: () => fetchFormSummaryFromRef,
31319
31624
  fetchMyForms: () => fetchMyForms,
31320
31625
  fetchResponses: () => fetchResponses,
@@ -31326,34 +31631,328 @@ __export(service_exports, {
31326
31631
  updateForm: () => updateForm
31327
31632
  });
31328
31633
 
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));
31634
+ // ../../node_modules/.pnpm/@noble+hashes@1.8.0/node_modules/@noble/hashes/esm/utils.js
31635
+ function isBytes4(a) {
31636
+ return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
31336
31637
  }
31337
- function makeSigningKeySigner(signingKeyHex) {
31338
- return new LocalSigner2(hexToBytes3(signingKeyHex));
31638
+ function abytes4(b, ...lengths) {
31639
+ if (!isBytes4(b))
31640
+ throw new Error("Uint8Array expected");
31641
+ if (lengths.length > 0 && !lengths.includes(b.length))
31642
+ throw new Error("Uint8Array expected of length " + lengths + ", got length=" + b.length);
31339
31643
  }
31340
- function encodeFormKeys(signingKeyHex, viewKeyHex) {
31341
- return viewKeyHex ? `${signingKeyHex}:${viewKeyHex}` : signingKeyHex;
31644
+ function aexists3(instance, checkFinished = true) {
31645
+ if (instance.destroyed)
31646
+ throw new Error("Hash instance has been destroyed");
31647
+ if (checkFinished && instance.finished)
31648
+ throw new Error("Hash#digest() has already been called");
31342
31649
  }
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) };
31650
+ function aoutput3(out, instance) {
31651
+ abytes4(out);
31652
+ const min = instance.outputLen;
31653
+ if (out.length < min) {
31654
+ throw new Error("digestInto() expects output buffer of length at least " + min);
31655
+ }
31347
31656
  }
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);
31657
+ function clean3(...arrays) {
31658
+ for (let i4 = 0; i4 < arrays.length; i4++) {
31659
+ arrays[i4].fill(0);
31354
31660
  }
31355
- return out;
31356
31661
  }
31662
+ function createView3(arr) {
31663
+ return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
31664
+ }
31665
+ function rotr2(word, shift) {
31666
+ return word << 32 - shift | word >>> shift;
31667
+ }
31668
+ function utf8ToBytes2(str) {
31669
+ if (typeof str !== "string")
31670
+ throw new Error("string expected");
31671
+ return new Uint8Array(new TextEncoder().encode(str));
31672
+ }
31673
+ function toBytes(data) {
31674
+ if (typeof data === "string")
31675
+ data = utf8ToBytes2(data);
31676
+ abytes4(data);
31677
+ return data;
31678
+ }
31679
+ var Hash = class {
31680
+ };
31681
+ function createHasher2(hashCons) {
31682
+ const hashC = (msg) => hashCons().update(toBytes(msg)).digest();
31683
+ const tmp = hashCons();
31684
+ hashC.outputLen = tmp.outputLen;
31685
+ hashC.blockLen = tmp.blockLen;
31686
+ hashC.create = () => hashCons();
31687
+ return hashC;
31688
+ }
31689
+
31690
+ // ../../node_modules/.pnpm/@noble+hashes@1.8.0/node_modules/@noble/hashes/esm/_md.js
31691
+ function setBigUint64(view, byteOffset, value, isLE3) {
31692
+ if (typeof view.setBigUint64 === "function")
31693
+ return view.setBigUint64(byteOffset, value, isLE3);
31694
+ const _32n = BigInt(32);
31695
+ const _u32_max = BigInt(4294967295);
31696
+ const wh = Number(value >> _32n & _u32_max);
31697
+ const wl = Number(value & _u32_max);
31698
+ const h = isLE3 ? 4 : 0;
31699
+ const l = isLE3 ? 0 : 4;
31700
+ view.setUint32(byteOffset + h, wh, isLE3);
31701
+ view.setUint32(byteOffset + l, wl, isLE3);
31702
+ }
31703
+ function Chi2(a, b, c) {
31704
+ return a & b ^ ~a & c;
31705
+ }
31706
+ function Maj2(a, b, c) {
31707
+ return a & b ^ a & c ^ b & c;
31708
+ }
31709
+ var HashMD2 = class extends Hash {
31710
+ constructor(blockLen, outputLen, padOffset, isLE3) {
31711
+ super();
31712
+ this.finished = false;
31713
+ this.length = 0;
31714
+ this.pos = 0;
31715
+ this.destroyed = false;
31716
+ this.blockLen = blockLen;
31717
+ this.outputLen = outputLen;
31718
+ this.padOffset = padOffset;
31719
+ this.isLE = isLE3;
31720
+ this.buffer = new Uint8Array(blockLen);
31721
+ this.view = createView3(this.buffer);
31722
+ }
31723
+ update(data) {
31724
+ aexists3(this);
31725
+ data = toBytes(data);
31726
+ abytes4(data);
31727
+ const { view, buffer, blockLen } = this;
31728
+ const len = data.length;
31729
+ for (let pos = 0; pos < len; ) {
31730
+ const take = Math.min(blockLen - this.pos, len - pos);
31731
+ if (take === blockLen) {
31732
+ const dataView = createView3(data);
31733
+ for (; blockLen <= len - pos; pos += blockLen)
31734
+ this.process(dataView, pos);
31735
+ continue;
31736
+ }
31737
+ buffer.set(data.subarray(pos, pos + take), this.pos);
31738
+ this.pos += take;
31739
+ pos += take;
31740
+ if (this.pos === blockLen) {
31741
+ this.process(view, 0);
31742
+ this.pos = 0;
31743
+ }
31744
+ }
31745
+ this.length += data.length;
31746
+ this.roundClean();
31747
+ return this;
31748
+ }
31749
+ digestInto(out) {
31750
+ aexists3(this);
31751
+ aoutput3(out, this);
31752
+ this.finished = true;
31753
+ const { buffer, view, blockLen, isLE: isLE3 } = this;
31754
+ let { pos } = this;
31755
+ buffer[pos++] = 128;
31756
+ clean3(this.buffer.subarray(pos));
31757
+ if (this.padOffset > blockLen - pos) {
31758
+ this.process(view, 0);
31759
+ pos = 0;
31760
+ }
31761
+ for (let i4 = pos; i4 < blockLen; i4++)
31762
+ buffer[i4] = 0;
31763
+ setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE3);
31764
+ this.process(view, 0);
31765
+ const oview = createView3(out);
31766
+ const len = this.outputLen;
31767
+ if (len % 4)
31768
+ throw new Error("_sha2: outputLen should be aligned to 32bit");
31769
+ const outLen = len / 4;
31770
+ const state = this.get();
31771
+ if (outLen > state.length)
31772
+ throw new Error("_sha2: outputLen bigger than state");
31773
+ for (let i4 = 0; i4 < outLen; i4++)
31774
+ oview.setUint32(4 * i4, state[i4], isLE3);
31775
+ }
31776
+ digest() {
31777
+ const { buffer, outputLen } = this;
31778
+ this.digestInto(buffer);
31779
+ const res = buffer.slice(0, outputLen);
31780
+ this.destroy();
31781
+ return res;
31782
+ }
31783
+ _cloneInto(to) {
31784
+ to || (to = new this.constructor());
31785
+ to.set(...this.get());
31786
+ const { blockLen, buffer, length, finished, destroyed, pos } = this;
31787
+ to.destroyed = destroyed;
31788
+ to.finished = finished;
31789
+ to.length = length;
31790
+ to.pos = pos;
31791
+ if (length % blockLen)
31792
+ to.buffer.set(buffer);
31793
+ return to;
31794
+ }
31795
+ clone() {
31796
+ return this._cloneInto();
31797
+ }
31798
+ };
31799
+ var SHA256_IV2 = /* @__PURE__ */ Uint32Array.from([
31800
+ 1779033703,
31801
+ 3144134277,
31802
+ 1013904242,
31803
+ 2773480762,
31804
+ 1359893119,
31805
+ 2600822924,
31806
+ 528734635,
31807
+ 1541459225
31808
+ ]);
31809
+
31810
+ // ../../node_modules/.pnpm/@noble+hashes@1.8.0/node_modules/@noble/hashes/esm/sha2.js
31811
+ var SHA256_K2 = /* @__PURE__ */ Uint32Array.from([
31812
+ 1116352408,
31813
+ 1899447441,
31814
+ 3049323471,
31815
+ 3921009573,
31816
+ 961987163,
31817
+ 1508970993,
31818
+ 2453635748,
31819
+ 2870763221,
31820
+ 3624381080,
31821
+ 310598401,
31822
+ 607225278,
31823
+ 1426881987,
31824
+ 1925078388,
31825
+ 2162078206,
31826
+ 2614888103,
31827
+ 3248222580,
31828
+ 3835390401,
31829
+ 4022224774,
31830
+ 264347078,
31831
+ 604807628,
31832
+ 770255983,
31833
+ 1249150122,
31834
+ 1555081692,
31835
+ 1996064986,
31836
+ 2554220882,
31837
+ 2821834349,
31838
+ 2952996808,
31839
+ 3210313671,
31840
+ 3336571891,
31841
+ 3584528711,
31842
+ 113926993,
31843
+ 338241895,
31844
+ 666307205,
31845
+ 773529912,
31846
+ 1294757372,
31847
+ 1396182291,
31848
+ 1695183700,
31849
+ 1986661051,
31850
+ 2177026350,
31851
+ 2456956037,
31852
+ 2730485921,
31853
+ 2820302411,
31854
+ 3259730800,
31855
+ 3345764771,
31856
+ 3516065817,
31857
+ 3600352804,
31858
+ 4094571909,
31859
+ 275423344,
31860
+ 430227734,
31861
+ 506948616,
31862
+ 659060556,
31863
+ 883997877,
31864
+ 958139571,
31865
+ 1322822218,
31866
+ 1537002063,
31867
+ 1747873779,
31868
+ 1955562222,
31869
+ 2024104815,
31870
+ 2227730452,
31871
+ 2361852424,
31872
+ 2428436474,
31873
+ 2756734187,
31874
+ 3204031479,
31875
+ 3329325298
31876
+ ]);
31877
+ var SHA256_W2 = /* @__PURE__ */ new Uint32Array(64);
31878
+ var SHA256 = class extends HashMD2 {
31879
+ constructor(outputLen = 32) {
31880
+ super(64, outputLen, 8, false);
31881
+ this.A = SHA256_IV2[0] | 0;
31882
+ this.B = SHA256_IV2[1] | 0;
31883
+ this.C = SHA256_IV2[2] | 0;
31884
+ this.D = SHA256_IV2[3] | 0;
31885
+ this.E = SHA256_IV2[4] | 0;
31886
+ this.F = SHA256_IV2[5] | 0;
31887
+ this.G = SHA256_IV2[6] | 0;
31888
+ this.H = SHA256_IV2[7] | 0;
31889
+ }
31890
+ get() {
31891
+ const { A, B, C, D, E, F, G, H } = this;
31892
+ return [A, B, C, D, E, F, G, H];
31893
+ }
31894
+ // prettier-ignore
31895
+ set(A, B, C, D, E, F, G, H) {
31896
+ this.A = A | 0;
31897
+ this.B = B | 0;
31898
+ this.C = C | 0;
31899
+ this.D = D | 0;
31900
+ this.E = E | 0;
31901
+ this.F = F | 0;
31902
+ this.G = G | 0;
31903
+ this.H = H | 0;
31904
+ }
31905
+ process(view, offset) {
31906
+ for (let i4 = 0; i4 < 16; i4++, offset += 4)
31907
+ SHA256_W2[i4] = view.getUint32(offset, false);
31908
+ for (let i4 = 16; i4 < 64; i4++) {
31909
+ const W15 = SHA256_W2[i4 - 15];
31910
+ const W2 = SHA256_W2[i4 - 2];
31911
+ const s0 = rotr2(W15, 7) ^ rotr2(W15, 18) ^ W15 >>> 3;
31912
+ const s1 = rotr2(W2, 17) ^ rotr2(W2, 19) ^ W2 >>> 10;
31913
+ SHA256_W2[i4] = s1 + SHA256_W2[i4 - 7] + s0 + SHA256_W2[i4 - 16] | 0;
31914
+ }
31915
+ let { A, B, C, D, E, F, G, H } = this;
31916
+ for (let i4 = 0; i4 < 64; i4++) {
31917
+ const sigma1 = rotr2(E, 6) ^ rotr2(E, 11) ^ rotr2(E, 25);
31918
+ const T1 = H + sigma1 + Chi2(E, F, G) + SHA256_K2[i4] + SHA256_W2[i4] | 0;
31919
+ const sigma0 = rotr2(A, 2) ^ rotr2(A, 13) ^ rotr2(A, 22);
31920
+ const T2 = sigma0 + Maj2(A, B, C) | 0;
31921
+ H = G;
31922
+ G = F;
31923
+ F = E;
31924
+ E = D + T1 | 0;
31925
+ D = C;
31926
+ C = B;
31927
+ B = A;
31928
+ A = T1 + T2 | 0;
31929
+ }
31930
+ A = A + this.A | 0;
31931
+ B = B + this.B | 0;
31932
+ C = C + this.C | 0;
31933
+ D = D + this.D | 0;
31934
+ E = E + this.E | 0;
31935
+ F = F + this.F | 0;
31936
+ G = G + this.G | 0;
31937
+ H = H + this.H | 0;
31938
+ this.set(A, B, C, D, E, F, G, H);
31939
+ }
31940
+ roundClean() {
31941
+ clean3(SHA256_W2);
31942
+ }
31943
+ destroy() {
31944
+ this.set(0, 0, 0, 0, 0, 0, 0, 0);
31945
+ clean3(this.buffer);
31946
+ }
31947
+ };
31948
+ var sha2562 = /* @__PURE__ */ createHasher2(() => new SHA256());
31949
+
31950
+ // ../../node_modules/.pnpm/@noble+hashes@1.8.0/node_modules/@noble/hashes/esm/sha256.js
31951
+ var sha2563 = sha2562;
31952
+
31953
+ // ../../node_modules/.pnpm/nostr-tools@2.23.3_typescript@5.9.3/node_modules/nostr-tools/lib/esm/utils.js
31954
+ var utf8Decoder7 = new TextDecoder("utf-8");
31955
+ var utf8Encoder7 = new TextEncoder();
31357
31956
 
31358
31957
  // ../agent/src/services/forms/types.ts
31359
31958
  var FORM_KINDS = {
@@ -31375,46 +31974,219 @@ var AnswerType = /* @__PURE__ */ ((AnswerType2) => {
31375
31974
  AnswerType2["datetime"] = "datetime";
31376
31975
  AnswerType2["fileUpload"] = "fileUpload";
31377
31976
  AnswerType2["signature"] = "signature";
31378
- AnswerType2["multiChoiceGrid"] = "multiChoiceGrid";
31977
+ AnswerType2["multipleChoiceGrid"] = "multipleChoiceGrid";
31379
31978
  AnswerType2["checkboxGrid"] = "checkboxGrid";
31979
+ AnswerType2["rating"] = "rating";
31380
31980
  AnswerType2["section"] = "section";
31381
31981
  return AnswerType2;
31382
31982
  })(AnswerType || {});
31383
31983
 
31984
+ // ../agent/src/services/forms/fieldCodec.ts
31985
+ var PRIMITIVE_BY_TYPE = {
31986
+ ["label" /* label */]: "label",
31987
+ ["section" /* section */]: "section",
31988
+ ["shortText" /* shortText */]: "text",
31989
+ ["paragraph" /* paragraph */]: "text",
31990
+ ["date" /* date */]: "text",
31991
+ ["time" /* time */]: "text",
31992
+ ["signature" /* signature */]: "text",
31993
+ ["number" /* number */]: "number",
31994
+ ["radioButton" /* radioButton */]: "option",
31995
+ ["checkboxes" /* checkboxes */]: "option",
31996
+ ["dropdown" /* dropdown */]: "option",
31997
+ ["fileUpload" /* fileUpload */]: "file",
31998
+ ["datetime" /* datetime */]: "datetime",
31999
+ ["multipleChoiceGrid" /* multipleChoiceGrid */]: "grid",
32000
+ ["checkboxGrid" /* checkboxGrid */]: "grid",
32001
+ ["rating" /* rating */]: "rating"
32002
+ };
32003
+ var DEFAULT_TYPE_BY_PRIMITIVE = {
32004
+ text: "shortText" /* shortText */,
32005
+ number: "number" /* number */,
32006
+ option: "radioButton" /* radioButton */,
32007
+ label: "label" /* label */,
32008
+ section: "section" /* section */,
32009
+ file: "fileUpload" /* fileUpload */,
32010
+ datetime: "datetime" /* datetime */,
32011
+ grid: "multipleChoiceGrid" /* multipleChoiceGrid */,
32012
+ rating: "rating" /* rating */
32013
+ };
32014
+ var GRID_TYPES = /* @__PURE__ */ new Set(["multipleChoiceGrid" /* multipleChoiceGrid */, "checkboxGrid" /* checkboxGrid */]);
32015
+ function makeId() {
32016
+ return Math.random().toString(36).slice(2, 8) || "id";
32017
+ }
32018
+ function buildFieldTag(field) {
32019
+ let options = "[]";
32020
+ if (GRID_TYPES.has(field.type)) {
32021
+ const grid = {
32022
+ rows: (field.gridRows ?? []).map((label) => [makeId(), label]),
32023
+ columns: (field.gridCols ?? []).map((label) => [makeId(), label])
32024
+ };
32025
+ options = JSON.stringify(grid);
32026
+ } else if (field.options) {
32027
+ options = JSON.stringify(field.options.map((o) => [o.id, o.label]));
32028
+ }
32029
+ const config2 = { renderElement: field.type };
32030
+ if (field.required !== void 0) config2.required = field.required;
32031
+ if (field.placeholder !== void 0) config2.placeholder = field.placeholder;
32032
+ if (field.type === "rating" /* rating */ && field.maxStars !== void 0) {
32033
+ config2.maxStars = field.maxStars;
32034
+ }
32035
+ if (GRID_TYPES.has(field.type)) {
32036
+ config2.allowMultiplePerRow = field.type === "checkboxGrid" /* checkboxGrid */;
32037
+ }
32038
+ if (field.validation) {
32039
+ const v = field.validation;
32040
+ const rules = {};
32041
+ if (v.min !== void 0) rules.min = { min: v.min };
32042
+ if (v.max !== void 0) rules.max = { max: v.max };
32043
+ if (v.regex !== void 0) rules.regex = { pattern: v.regex, errorMessage: v.regexError ?? "" };
32044
+ if (Object.keys(rules).length > 0) config2.validationRules = rules;
32045
+ }
32046
+ if (field.fileConfig) {
32047
+ const f = field.fileConfig;
32048
+ if (f.blossomServer !== void 0) config2.blossomServer = f.blossomServer;
32049
+ if (f.maxBytes !== void 0) config2.maxFileSize = f.maxBytes / (1024 * 1024);
32050
+ if (f.mimeTypes !== void 0) config2.allowedTypes = f.mimeTypes;
32051
+ }
32052
+ return ["field", field.id, PRIMITIVE_BY_TYPE[field.type], field.label, options, JSON.stringify(config2)];
32053
+ }
32054
+ function isAnswerType(value) {
32055
+ return Object.values(AnswerType).includes(value);
32056
+ }
32057
+ function resolveType(primitive, renderElement) {
32058
+ if (primitive === "section") return "section" /* section */;
32059
+ if (renderElement) {
32060
+ if (renderElement === "multiChoiceGrid") return "multipleChoiceGrid" /* multipleChoiceGrid */;
32061
+ if (isAnswerType(renderElement)) return renderElement;
32062
+ }
32063
+ if (primitive === "multiChoiceGrid") return "multipleChoiceGrid" /* multipleChoiceGrid */;
32064
+ if (isAnswerType(primitive)) return primitive;
32065
+ return DEFAULT_TYPE_BY_PRIMITIVE[primitive] ?? "shortText" /* shortText */;
32066
+ }
32067
+ function parseFieldTag(tag) {
32068
+ const [, id = "", primitive = "text", label = "", optionsJson, configJson] = tag;
32069
+ let config2 = {};
32070
+ if (configJson) {
32071
+ try {
32072
+ const parsed = JSON.parse(configJson);
32073
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) config2 = parsed;
32074
+ } catch {
32075
+ }
32076
+ }
32077
+ const field = {
32078
+ id,
32079
+ type: resolveType(primitive, config2.renderElement),
32080
+ label
32081
+ };
32082
+ let parsedOptions;
32083
+ if (optionsJson) {
32084
+ try {
32085
+ parsedOptions = JSON.parse(optionsJson);
32086
+ } catch {
32087
+ }
32088
+ }
32089
+ if (Array.isArray(parsedOptions) && parsedOptions.length > 0) {
32090
+ field.options = parsedOptions.map(
32091
+ (o) => ({ id: o[0], label: o[1] })
32092
+ );
32093
+ } else if (parsedOptions && typeof parsedOptions === "object") {
32094
+ const grid = parsedOptions;
32095
+ if (Array.isArray(grid.rows)) field.gridRows = grid.rows.map((r) => r[1]);
32096
+ if (Array.isArray(grid.columns)) field.gridCols = grid.columns.map((c) => c[1]);
32097
+ }
32098
+ if (typeof config2.required === "boolean") field.required = config2.required;
32099
+ if (typeof config2.placeholder === "string") field.placeholder = config2.placeholder;
32100
+ if (typeof config2.maxStars === "number") field.maxStars = config2.maxStars;
32101
+ const rules = config2.validationRules;
32102
+ if (rules && typeof rules === "object") {
32103
+ const validation = {};
32104
+ if (typeof rules.range?.min === "number") validation.min = rules.range.min;
32105
+ if (typeof rules.range?.max === "number") validation.max = rules.range.max;
32106
+ if (typeof rules.min?.min === "number") validation.min = rules.min.min;
32107
+ if (typeof rules.max?.max === "number") validation.max = rules.max.max;
32108
+ if (typeof rules.regex?.pattern === "string") {
32109
+ validation.regex = rules.regex.pattern;
32110
+ if (rules.regex.errorMessage) validation.regexError = rules.regex.errorMessage;
32111
+ }
32112
+ if (Object.keys(validation).length > 0) field.validation = validation;
32113
+ }
32114
+ if (config2.blossomServer !== void 0 || config2.maxFileSize !== void 0 || config2.allowedTypes !== void 0) {
32115
+ field.fileConfig = {
32116
+ ...typeof config2.blossomServer === "string" && { blossomServer: config2.blossomServer },
32117
+ ...typeof config2.maxFileSize === "number" && {
32118
+ maxBytes: config2.maxFileSize * 1024 * 1024
32119
+ },
32120
+ ...Array.isArray(config2.allowedTypes) && { mimeTypes: config2.allowedTypes }
32121
+ };
32122
+ }
32123
+ return field;
32124
+ }
32125
+
32126
+ // ../agent/src/services/forms/keys.ts
32127
+ function makeViewKeySigner(viewKeyHex) {
32128
+ return new LocalSigner2(hexToBytes3(viewKeyHex));
32129
+ }
32130
+ function makeSigningKeySigner(signingKeyHex) {
32131
+ return new LocalSigner2(hexToBytes3(signingKeyHex));
32132
+ }
32133
+ function encodeFormKeys(signingKeyHex, viewKeyHex) {
32134
+ return viewKeyHex ? `${signingKeyHex}:${viewKeyHex}` : signingKeyHex;
32135
+ }
32136
+ function decodeFormKeys(segment) {
32137
+ const idx = segment.indexOf(":");
32138
+ if (idx < 0) return { signingKey: segment };
32139
+ return { signingKey: segment.slice(0, idx), viewKey: segment.slice(idx + 1) };
32140
+ }
32141
+ function hexToBytes3(hex) {
32142
+ if (hex.length % 2 !== 0) throw new Error("invalid hex length");
32143
+ if (hex.length > 0 && !/^[0-9a-fA-F]+$/.test(hex)) throw new Error("invalid hex characters");
32144
+ const out = new Uint8Array(hex.length / 2);
32145
+ for (let i4 = 0; i4 < out.length; i4++) {
32146
+ out[i4] = parseInt(hex.slice(i4 * 2, i4 * 2 + 2), 16);
32147
+ }
32148
+ return out;
32149
+ }
32150
+
31384
32151
  // ../agent/src/services/forms/service.ts
32152
+ function relaysForForm(formRelays) {
32153
+ return Array.from(/* @__PURE__ */ new Set([...relayManager.getRelaysForModule("forms"), ...formRelays ?? []]));
32154
+ }
32155
+ function buildSpecRows(formId, name, fields, settings) {
32156
+ const rows = [
32157
+ ["d", formId],
32158
+ ["name", name]
32159
+ ];
32160
+ if (settings) rows.push(["settings", JSON.stringify(settings)]);
32161
+ for (const field of fields) rows.push(buildFieldTag(field));
32162
+ return rows;
32163
+ }
32164
+ function buildEncryptedOuterTags(formId, name, relays, settings) {
32165
+ const tags = [["d", formId], ["name", name], ...relays.map((r) => ["relay", r])];
32166
+ const allowed = settings?.allowedResponders ?? [];
32167
+ const pTags = /* @__PURE__ */ new Set([...allowed, ...settings?.collaborators ?? []]);
32168
+ for (const pk of allowed) tags.push(["allowed", pk]);
32169
+ for (const pk of pTags) tags.push(["p", pk]);
32170
+ return tags;
32171
+ }
31385
32172
  async function createForm(params) {
31386
- const signer = await signerManager.getSigner();
31387
- const userPubkey = await signer.getPublicKey();
32173
+ await signerManager.getSigner();
31388
32174
  const formId = crypto.randomUUID().slice(0, 8);
31389
32175
  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
- }
32176
+ const specRows = buildSpecRows(formId, params.name, params.fields, params.settings);
32177
+ const signingKey = generateSecretKey();
32178
+ const signingKeyHex = bytesToHex(signingKey);
32179
+ const signingPubkey = getPublicKey(signingKey);
31398
32180
  if (params.encrypt) {
31399
- const signingKey = generateSecretKey();
31400
- const signingKeyHex = bytesToHex(signingKey);
31401
- const signingPubkey = getPublicKey(signingKey);
31402
32181
  const viewKey = generateSecretKey();
31403
32182
  const viewKeyHex = bytesToHex(viewKey);
31404
32183
  const viewPubkey = getPublicKey(viewKey);
31405
32184
  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)]);
32185
+ const content = await formSigner.nip44Encrypt(viewPubkey, JSON.stringify(specRows));
31414
32186
  const event2 = {
31415
32187
  kind: FORM_KINDS.template,
31416
32188
  created_at: Math.floor(Date.now() / 1e3),
31417
- tags: encTags,
32189
+ tags: buildEncryptedOuterTags(formId, params.name, relays, params.settings),
31418
32190
  content
31419
32191
  };
31420
32192
  const signed2 = finalizeEvent(event2, signingKey);
@@ -31422,20 +32194,20 @@ async function createForm(params) {
31422
32194
  await appendToMyFormsList(signingPubkey, formId, relays[0] ?? "", signingKeyHex, viewKeyHex);
31423
32195
  return { formId, pubkey: signingPubkey, signingKey: signingKeyHex, viewKey: viewKeyHex };
31424
32196
  }
31425
- if (params.settings?.publicForm) baseTags.push(["t", "public"]);
32197
+ const tags = [...specRows, ["t", "public"], ...relays.map((r) => ["relay", r])];
31426
32198
  const event = {
31427
32199
  kind: FORM_KINDS.template,
31428
32200
  created_at: Math.floor(Date.now() / 1e3),
31429
- tags: baseTags,
32201
+ tags,
31430
32202
  content: ""
31431
32203
  };
31432
- const signed = await signer.signEvent(event);
32204
+ const signed = finalizeEvent(event, signingKey);
31433
32205
  await nostrRuntime.publish(relays, signed);
31434
- await appendToMyFormsList(userPubkey, formId, relays[0] ?? "", void 0, void 0);
31435
- return { formId, pubkey: userPubkey };
32206
+ await appendToMyFormsList(signingPubkey, formId, relays[0] ?? "", signingKeyHex, void 0);
32207
+ return { formId, pubkey: signingPubkey, signingKey: signingKeyHex };
31436
32208
  }
31437
- async function fetchForm(pubkey, formId, viewKey) {
31438
- const relays = relayManager.getRelaysForModule("forms");
32209
+ async function fetchForm(pubkey, formId, viewKey, relayHints) {
32210
+ const relays = relaysForForm(relayHints);
31439
32211
  const event = await nostrRuntime.fetchOne(relays, {
31440
32212
  kinds: [FORM_KINDS.template],
31441
32213
  authors: [pubkey],
@@ -31443,25 +32215,48 @@ async function fetchForm(pubkey, formId, viewKey) {
31443
32215
  limit: 1
31444
32216
  });
31445
32217
  if (!event) return null;
31446
- const template = parseFormEvent(event);
31447
- if (template.isEncrypted && event.content && viewKey) {
32218
+ let effectiveViewKey = viewKey;
32219
+ if (!effectiveViewKey && event.content && !event.tags.some((t) => t[0] === "field")) {
32220
+ const keys = await fetchFormKeys(pubkey, formId).catch(() => null);
32221
+ effectiveViewKey = keys?.viewKey;
32222
+ }
32223
+ let decryptedRows;
32224
+ if (event.content && effectiveViewKey) {
31448
32225
  try {
31449
- const viewSigner = makeViewKeySigner(viewKey);
32226
+ const viewSigner = makeViewKeySigner(effectiveViewKey);
31450
32227
  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
- }));
32228
+ const rows = JSON.parse(decrypted);
32229
+ if (Array.isArray(rows)) decryptedRows = rows;
32230
+ } catch {
32231
+ }
32232
+ }
32233
+ return parseFormEvent(event, decryptedRows);
32234
+ }
32235
+ function accessGrantAlias(formAuthor, formId, recipient) {
32236
+ return bytesToHex(sha2563(`${FORM_KINDS.template}:${formAuthor}:${formId}:${recipient}`));
32237
+ }
32238
+ async function fetchFormKeys(formAuthor, formId) {
32239
+ const signer = signerManager.getSignerIfAvailable();
32240
+ if (!signer?.nip44Decrypt) return null;
32241
+ const userPub = await signer.getPublicKey();
32242
+ const relays = relayManager.getRelaysForModule("forms");
32243
+ const wraps = await nostrRuntime.querySync(relays, {
32244
+ kinds: [FORM_KINDS.giftWrap],
32245
+ "#p": [accessGrantAlias(formAuthor, formId, userPub)]
32246
+ });
32247
+ for (const wrap of wraps) {
32248
+ try {
32249
+ const seal = JSON.parse(await signer.nip44Decrypt(wrap.pubkey, wrap.content));
32250
+ const rumor = JSON.parse(await signer.nip44Decrypt(seal.pubkey, seal.content));
32251
+ const viewKey = rumor.tags?.find((t) => t[0] === "ViewAccess")?.[1];
32252
+ const signingKey = rumor.tags?.find((t) => t[0] === "EditAccess")?.[1];
32253
+ if (viewKey || signingKey) return { viewKey, signingKey };
31459
32254
  } catch {
31460
32255
  }
31461
32256
  }
31462
- return template;
32257
+ return null;
31463
32258
  }
31464
- async function submitResponse(formPubkey, formId, responses, encrypt7 = false, overrideSigner) {
32259
+ async function submitResponse(formPubkey, formId, responses, encrypt7 = false, overrideSigner, formRelays) {
31465
32260
  const signer = overrideSigner ?? await signerManager.getSigner();
31466
32261
  const responseTags = responses.map((r) => ["response", r.fieldId, r.answer, r.metadata ?? ""]);
31467
32262
  const tags = [
@@ -31480,11 +32275,10 @@ async function submitResponse(formPubkey, formId, responses, encrypt7 = false, o
31480
32275
  content
31481
32276
  };
31482
32277
  const signed = await signer.signEvent(event);
31483
- const relays = relayManager.getRelaysForModule("forms");
31484
- await nostrRuntime.publish(relays, signed);
32278
+ await nostrRuntime.publish(relaysForForm(formRelays), signed);
31485
32279
  }
31486
- function subscribeToResponses(formPubkey, formId, onResponse, onEose, signingKey) {
31487
- const relays = relayManager.getRelaysForModule("forms");
32280
+ function subscribeToResponses(formPubkey, formId, onResponse, onEose, signingKey, formRelays) {
32281
+ const relays = relaysForForm(formRelays);
31488
32282
  const formSigner = signingKey ? makeSigningKeySigner(signingKey) : void 0;
31489
32283
  return nostrRuntime.subscribe(
31490
32284
  relays,
@@ -31508,9 +32302,8 @@ function subscribeToResponses(formPubkey, formId, onResponse, onEose, signingKey
31508
32302
  }
31509
32303
  );
31510
32304
  }
31511
- async function fetchResponses(formPubkey, formId, signingKey) {
31512
- const relays = relayManager.getRelaysForModule("forms");
31513
- const events = await nostrRuntime.querySync(relays, {
32305
+ async function fetchResponses(formPubkey, formId, signingKey, formRelays) {
32306
+ const events = await nostrRuntime.querySync(relaysForForm(formRelays), {
31514
32307
  kinds: [FORM_KINDS.response],
31515
32308
  "#a": [`${FORM_KINDS.template}:${formPubkey}:${formId}`]
31516
32309
  });
@@ -31543,6 +32336,27 @@ async function fetchLatestMyFormsEvent(relays, userPubkey) {
31543
32336
  null
31544
32337
  );
31545
32338
  }
32339
+ async function decryptListEntries(signer, userPubkey, content) {
32340
+ let decrypted = "";
32341
+ if (content.includes("?iv=")) {
32342
+ if (!signer.decrypt) throw new Error("Signer cannot decrypt");
32343
+ decrypted = await signer.decrypt(userPubkey, content);
32344
+ } else {
32345
+ decrypted = await nip44SelfDecrypt(signer, content);
32346
+ }
32347
+ const parsed = JSON.parse(decrypted);
32348
+ return Array.isArray(parsed) ? parsed : [];
32349
+ }
32350
+ async function publishMyFormsList(signer, relays, entries) {
32351
+ const encrypted = await nip44SelfEncrypt(signer, JSON.stringify(entries));
32352
+ const event = {
32353
+ kind: FORM_KINDS.myFormsList,
32354
+ created_at: Math.floor(Date.now() / 1e3),
32355
+ tags: [],
32356
+ content: encrypted
32357
+ };
32358
+ await nostrRuntime.publish(relays, await signer.signEvent(event));
32359
+ }
31546
32360
  async function fetchMyForms() {
31547
32361
  const signer = await signerManager.getSigner();
31548
32362
  const userPubkey = await signer.getPublicKey();
@@ -31551,15 +32365,7 @@ async function fetchMyForms() {
31551
32365
  let entries = [];
31552
32366
  if (listEvent?.content) {
31553
32367
  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 : [];
32368
+ entries = await decryptListEntries(signer, userPubkey, listEvent.content);
31563
32369
  } catch {
31564
32370
  }
31565
32371
  }
@@ -31577,7 +32383,7 @@ async function fetchMyForms() {
31577
32383
  if (d) eventMap.set(`${evt.pubkey}:${d}`, evt);
31578
32384
  }
31579
32385
  return entries.filter((e) => e[0] === "f" && e[1]).map((entry) => {
31580
- const [, coordKey, , keySegment] = entry;
32386
+ const [, coordKey, relayHint, keySegment] = entry;
31581
32387
  const [formPubkey, formId] = coordKey.split(":");
31582
32388
  if (!formPubkey || !formId) return null;
31583
32389
  const evt = eventMap.get(coordKey);
@@ -31593,7 +32399,8 @@ async function fetchMyForms() {
31593
32399
  createdAt: evt?.created_at ?? 0,
31594
32400
  isEncrypted,
31595
32401
  signingKey: keys?.signingKey,
31596
- viewKey: keys?.viewKey
32402
+ viewKey: keys?.viewKey,
32403
+ relay: relayHint || void 0
31597
32404
  };
31598
32405
  return summary;
31599
32406
  }).filter((s) => s !== null);
@@ -31606,15 +32413,7 @@ async function appendToMyFormsList(formPubkey, formId, relay, signingKeyHex, vie
31606
32413
  let entries = [];
31607
32414
  if (existing?.content) {
31608
32415
  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 : [];
32416
+ entries = await decryptListEntries(signer, userPubkey, existing.content);
31618
32417
  } catch {
31619
32418
  entries = [];
31620
32419
  }
@@ -31627,15 +32426,7 @@ async function appendToMyFormsList(formPubkey, formId, relay, signingKeyHex, vie
31627
32426
  const secrets = signingKeyHex ? encodeFormKeys(signingKeyHex, viewKeyHex) : "";
31628
32427
  entries.push(["f", coordKey, relay, secrets]);
31629
32428
  }
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);
32429
+ await publishMyFormsList(signer, relays, entries);
31639
32430
  }
31640
32431
  async function saveToMyForms(summaries) {
31641
32432
  const signer = await signerManager.getSigner();
@@ -31643,18 +32434,10 @@ async function saveToMyForms(summaries) {
31643
32434
  const entries = summaries.map((s) => [
31644
32435
  "f",
31645
32436
  `${s.pubkey}:${s.id}`,
31646
- "",
32437
+ s.relay ?? "",
31647
32438
  s.signingKey ? encodeFormKeys(s.signingKey, s.viewKey) : ""
31648
32439
  ]);
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);
32440
+ await publishMyFormsList(signer, relays, entries);
31658
32441
  }
31659
32442
  async function fetchMyFormsByAuthor(pubkey, relays) {
31660
32443
  const filter = {
@@ -31677,6 +32460,8 @@ async function fetchMyFormsByAuthor(pubkey, relays) {
31677
32460
  }
31678
32461
  async function deleteForm(formId, formPubkey) {
31679
32462
  const signer = await signerManager.getSigner();
32463
+ const userPubkey = await signer.getPublicKey();
32464
+ const relays = relayManager.getRelaysForModule("forms");
31680
32465
  const coordinate = `${FORM_KINDS.template}:${formPubkey}:${formId}`;
31681
32466
  const event = {
31682
32467
  kind: 5,
@@ -31687,58 +32472,60 @@ async function deleteForm(formId, formPubkey) {
31687
32472
  ],
31688
32473
  content: "Deleted via Formstr"
31689
32474
  };
31690
- const signed = await signer.signEvent(event);
31691
- const relays = relayManager.getRelaysForModule("forms");
31692
- await nostrRuntime.publish(relays, signed);
32475
+ await nostrRuntime.publish(relays, await signer.signEvent(event));
32476
+ const listEvent = await fetchLatestMyFormsEvent(relays, userPubkey);
32477
+ if (!listEvent?.content) return;
32478
+ let entries;
32479
+ try {
32480
+ entries = await decryptListEntries(signer, userPubkey, listEvent.content);
32481
+ } catch {
32482
+ return;
32483
+ }
32484
+ const coordKey = `${formPubkey}:${formId}`;
32485
+ const trimmed = entries.filter((e) => e[1] !== coordKey);
32486
+ if (trimmed.length === entries.length) return;
32487
+ await publishMyFormsList(signer, relays, trimmed);
31693
32488
  }
31694
32489
  async function updateForm(params) {
31695
32490
  const relays = relayManager.getRelaysForModule("forms");
31696
- const existing = await fetchForm(params.pubkey, params.formId);
32491
+ const summary = (await fetchMyForms()).find(
32492
+ (f) => f.id === params.formId && f.pubkey === params.pubkey
32493
+ );
32494
+ const existing = await fetchForm(params.pubkey, params.formId, summary?.viewKey);
31697
32495
  if (!existing) throw new Error(`Form not found: ${params.formId}`);
31698
32496
  const name = params.name ?? existing.name;
31699
32497
  const settings = { ...existing.settings, ...params.settings };
31700
32498
  const fields = params.fields ?? existing.fields;
32499
+ const specRows = buildSpecRows(params.formId, name, fields, settings);
31701
32500
  if (existing.isEncrypted) {
31702
- const summary = (await fetchMyForms()).find(
31703
- (f) => f.id === params.formId && f.pubkey === params.pubkey
31704
- );
31705
32501
  if (!summary?.signingKey || !summary?.viewKey) {
31706
32502
  throw new Error("Not the form owner or signing key unavailable");
31707
32503
  }
31708
32504
  const signingKeyBytes = hexToBytes3(summary.signingKey);
31709
32505
  const formSigner = makeSigningKeySigner(summary.signingKey);
31710
32506
  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
- ];
32507
+ const content = await formSigner.nip44Encrypt(viewPubkey, JSON.stringify(specRows));
31719
32508
  const event2 = {
31720
32509
  kind: FORM_KINDS.template,
31721
32510
  created_at: Math.floor(Date.now() / 1e3),
31722
- tags: tags2,
32511
+ tags: buildEncryptedOuterTags(params.formId, name, relays, settings),
31723
32512
  content
31724
32513
  };
31725
32514
  await nostrRuntime.publish(relays, finalizeEvent(event2, signingKeyBytes));
31726
32515
  return;
31727
32516
  }
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"]);
32517
+ const tags = [...specRows, ["t", "public"], ...relays.map((r) => ["relay", r])];
31736
32518
  const event = {
31737
32519
  kind: FORM_KINDS.template,
31738
32520
  created_at: Math.floor(Date.now() / 1e3),
31739
32521
  tags,
31740
32522
  content: ""
31741
32523
  };
32524
+ if (summary?.signingKey) {
32525
+ await nostrRuntime.publish(relays, finalizeEvent(event, hexToBytes3(summary.signingKey)));
32526
+ return;
32527
+ }
32528
+ const signer = await signerManager.getSigner();
31742
32529
  await nostrRuntime.publish(relays, await signer.signEvent(event));
31743
32530
  }
31744
32531
  async function shareForm(params) {
@@ -31746,27 +32533,50 @@ async function shareForm(params) {
31746
32533
  const summary = (await fetchMyForms()).find(
31747
32534
  (f) => f.id === params.formId && f.pubkey === params.formPubkey
31748
32535
  );
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
- };
32536
+ if (!summary?.signingKey) {
32537
+ throw new Error("Not the form owner or form keys unavailable");
32538
+ }
32539
+ const signingKeyBytes = hexToBytes3(summary.signingKey);
32540
+ const signingPubkey = getPublicKey(signingKeyBytes);
32541
+ const formSigner = new LocalSigner2(signingKeyBytes);
32542
+ const now2 = () => Math.round(Date.now() / 1e3);
32543
+ const editors = new Set(params.editors ?? []);
32544
+ const targets = [.../* @__PURE__ */ new Set([...params.recipients, ...editors])];
31764
32545
  const failed = [];
31765
32546
  let published = 0;
31766
- for (const recipient of params.recipients) {
32547
+ for (const recipient of targets) {
31767
32548
  try {
31768
- const wraps = await wrapManyEvents3(rumor, signer, [recipient]);
31769
- for (const wrap of wraps) await nostrRuntime.publish(relays, wrap);
32549
+ const accessTags = [];
32550
+ if (editors.has(recipient)) accessTags.push(["EditAccess", summary.signingKey]);
32551
+ if (summary.viewKey) accessTags.push(["ViewAccess", summary.viewKey]);
32552
+ const rumor = {
32553
+ kind: 18,
32554
+ pubkey: signingPubkey,
32555
+ created_at: now2(),
32556
+ content: "",
32557
+ tags: accessTags
32558
+ };
32559
+ rumor.id = getEventHash(rumor);
32560
+ const seal = finalizeEvent(
32561
+ {
32562
+ kind: 13,
32563
+ content: await formSigner.nip44Encrypt(recipient, JSON.stringify(rumor)),
32564
+ created_at: now2(),
32565
+ tags: []
32566
+ },
32567
+ signingKeyBytes
32568
+ );
32569
+ const randomKey = generateSecretKey();
32570
+ const wrap = finalizeEvent(
32571
+ {
32572
+ kind: 1059,
32573
+ content: await new LocalSigner2(randomKey).nip44Encrypt(recipient, JSON.stringify(seal)),
32574
+ created_at: now2(),
32575
+ tags: [["p", accessGrantAlias(signingPubkey, params.formId, recipient)]]
32576
+ },
32577
+ randomKey
32578
+ );
32579
+ await nostrRuntime.publish(relays, wrap);
31770
32580
  published++;
31771
32581
  } catch {
31772
32582
  failed.push(recipient);
@@ -31790,32 +32600,32 @@ async function importForm(summary) {
31790
32600
  if (current.some((f) => f.id === summary.id && f.pubkey === summary.pubkey)) return;
31791
32601
  await saveToMyForms([...current, summary]);
31792
32602
  }
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];
32603
+ function parseFormEvent(event, decryptedRows) {
32604
+ const merged = decryptedRows ? [...event.tags, ...decryptedRows] : event.tags;
32605
+ const dTag = merged.find((t) => t[0] === "d")?.[1] ?? "";
32606
+ const nameTag = merged.find((t) => t[0] === "name")?.[1] ?? "Untitled";
32607
+ const settingsTag = merged.find((t) => t[0] === "settings")?.[1];
31802
32608
  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;
32609
+ const relays = event.tags.filter((t) => t[0] === "relay" && t[1]).map((t) => t[1]);
32610
+ const fields = merged.filter((t) => t[0] === "field").map(parseFieldTag);
32611
+ const outerHasFields = event.tags.some((t) => t[0] === "field");
32612
+ const isEncrypted = encTag != null ? encTag === "view-key" : event.content.length > 0 && !outerHasFields;
32613
+ let settings = {};
32614
+ if (settingsTag) {
32615
+ try {
32616
+ settings = JSON.parse(settingsTag);
32617
+ } catch {
32618
+ }
32619
+ }
31811
32620
  return {
31812
32621
  id: dTag,
31813
32622
  name: nameTag,
31814
32623
  fields,
31815
- settings: settingsTag ? JSON.parse(settingsTag) : {},
32624
+ settings,
31816
32625
  pubkey: event.pubkey,
31817
32626
  createdAt: event.created_at,
31818
32627
  isEncrypted,
32628
+ ...relays.length > 0 && { relays },
31819
32629
  event
31820
32630
  };
31821
32631
  }
@@ -31831,15 +32641,6 @@ function parseResponseEvent(event) {
31831
32641
  event
31832
32642
  };
31833
32643
  }
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
32644
 
31844
32645
  // ../agent/src/services/calendar/service.ts
31845
32646
  var service_exports2 = {};
@@ -31854,9 +32655,14 @@ __export(service_exports2, {
31854
32655
  fetchCalendarLists: () => fetchCalendarLists,
31855
32656
  fetchDeletions: () => fetchDeletions,
31856
32657
  fetchInvitationsSync: () => fetchInvitationsSync,
32658
+ fetchParticipantRemovals: () => fetchParticipantRemovals,
32659
+ fetchRelayListsForPubkeys: () => fetchRelayListsForPubkeys,
32660
+ getInvitationInboxRelays: () => getInvitationInboxRelays,
31857
32661
  isEventDeleted: () => isEventDeleted,
32662
+ lookupEventViewKey: () => lookupEventViewKey,
31858
32663
  moveEventBetweenCalendarLists: () => moveEventBetweenCalendarLists,
31859
32664
  parseCalendarEvent: () => parseCalendarEvent,
32665
+ publishParticipantRemovalEvent: () => publishParticipantRemovalEvent,
31860
32666
  publishPrivateCalendarEvent: () => publishPrivateCalendarEvent,
31861
32667
  publishPublicCalendarEvent: () => publishPublicCalendarEvent,
31862
32668
  removeEventFromCalendarList: () => removeEventFromCalendarList,
@@ -31873,6 +32679,7 @@ function encodeCalendarList(list) {
31873
32679
  ["content", list.description ?? ""],
31874
32680
  ["color", list.color]
31875
32681
  ];
32682
+ if (list.notificationPreference === "disabled") tags.push(["notifications", "disabled"]);
31876
32683
  for (const ref of list.eventRefs) tags.push(["a", ...ref]);
31877
32684
  return tags;
31878
32685
  }
@@ -31880,6 +32687,7 @@ function decodeCalendarList(tags, dTag, eventId) {
31880
32687
  let title = DEFAULT_TITLE;
31881
32688
  let description = "";
31882
32689
  let color = DEFAULT_COLOR;
32690
+ let notificationPreference;
31883
32691
  const eventRefs = [];
31884
32692
  for (const tag of tags) {
31885
32693
  if (!Array.isArray(tag) || tag.length === 0) continue;
@@ -31893,6 +32701,9 @@ function decodeCalendarList(tags, dTag, eventId) {
31893
32701
  case "color":
31894
32702
  color = tag[1] || DEFAULT_COLOR;
31895
32703
  break;
32704
+ case "notifications":
32705
+ notificationPreference = tag[1] === "disabled" ? "disabled" : "enabled";
32706
+ break;
31896
32707
  case "a":
31897
32708
  if (tag[1] === "a") {
31898
32709
  const coord = tag[2] ?? "";
@@ -31911,7 +32722,8 @@ function decodeCalendarList(tags, dTag, eventId) {
31911
32722
  color,
31912
32723
  eventRefs,
31913
32724
  createdAt: 0,
31914
- isVisible: true
32725
+ isVisible: true,
32726
+ notificationPreference
31915
32727
  };
31916
32728
  }
31917
32729
 
@@ -31936,6 +32748,8 @@ var CALENDAR_KINDS = {
31936
32748
  rsvpGiftWrap: 1055,
31937
32749
  rsvpRumor: 55,
31938
32750
  participantRemoval: 84,
32751
+ /** Public free/busy list, one per (user, YYYY-MM month). */
32752
+ publicBusyList: 31926,
31939
32753
  // Appointment scheduling (Calendly-style booking links).
31940
32754
  schedulingPage: 31927,
31941
32755
  schedulingPagesList: 32680,
@@ -32181,13 +32995,14 @@ async function publishPrivateCalendarEvent(draft, calendarId) {
32181
32995
  const eventData = [
32182
32996
  ["title", draft.title],
32183
32997
  ["description", draft.description],
32184
- ["start", String(Math.floor(draft.begin.getTime() / 1e3))],
32185
- ["end", String(Math.floor(draft.end.getTime() / 1e3))],
32998
+ ["start", Math.floor(draft.begin.getTime() / 1e3)],
32999
+ ["end", Math.floor(draft.end.getTime() / 1e3)],
32186
33000
  ["d", eventId]
32187
33001
  ];
32188
33002
  if (draft.image) eventData.push(["image", draft.image]);
32189
33003
  if (draft.location) eventData.push(["location", draft.location]);
32190
33004
  for (const cat of draft.categories ?? []) eventData.push(["t", cat]);
33005
+ eventData.push(["p", pubkey]);
32191
33006
  for (const p of draft.participants ?? []) eventData.push(["p", p]);
32192
33007
  if (draft.startTzid) eventData.push(["start_tzid", draft.startTzid]);
32193
33008
  if (draft.endTzid) eventData.push(["end_tzid", draft.endTzid]);
@@ -32195,7 +33010,12 @@ async function publishPrivateCalendarEvent(draft, calendarId) {
32195
33010
  eventData.push(["L", "rrule"]);
32196
33011
  eventData.push(["l", draft.rrule]);
32197
33012
  }
32198
- if (draft.registrationFormRef) eventData.push(["form", draft.registrationFormRef]);
33013
+ if (draft.notificationPreference) eventData.push(["notification", draft.notificationPreference]);
33014
+ if (draft.registrationFormRef) {
33015
+ eventData.push(
33016
+ draft.registrationFormViewKey ? ["form", draft.registrationFormRef, draft.registrationFormViewKey] : ["form", draft.registrationFormRef]
33017
+ );
33018
+ }
32199
33019
  const viewKeyNsec = draft.viewKey ?? generateViewKey().nsec;
32200
33020
  const content = await encryptWithViewKey(viewKeyNsec, JSON.stringify(eventData));
32201
33021
  const event = {
@@ -32210,6 +33030,7 @@ async function publishPrivateCalendarEvent(draft, calendarId) {
32210
33030
  const relayHint = relays[0] ?? "";
32211
33031
  const coordinate = `${CALENDAR_KINDS.privateEvent}:${pubkey}:${eventId}`;
32212
33032
  if (draft.participants?.length) {
33033
+ const relayLists = await fetchRelayListsForPubkeys(draft.participants);
32213
33034
  for (const participant of draft.participants) {
32214
33035
  const wrap = await wrapEvent3(
32215
33036
  {
@@ -32224,7 +33045,7 @@ async function publishPrivateCalendarEvent(draft, calendarId) {
32224
33045
  participant,
32225
33046
  CALENDAR_KINDS.giftWrap
32226
33047
  );
32227
- await nostrRuntime.publish(relays, wrap);
33048
+ await nostrRuntime.publish(relayLists.get(participant) ?? relays, wrap);
32228
33049
  }
32229
33050
  }
32230
33051
  return {
@@ -32250,9 +33071,37 @@ async function publishPrivateCalendarEvent(draft, calendarId) {
32250
33071
  startTzid: draft.startTzid,
32251
33072
  endTzid: draft.endTzid,
32252
33073
  registrationFormRef: draft.registrationFormRef,
33074
+ registrationFormViewKey: draft.registrationFormViewKey,
33075
+ notificationPreference: draft.notificationPreference,
32253
33076
  event: signed
32254
33077
  };
32255
33078
  }
33079
+ async function fetchRelayListsForPubkeys(pubkeys) {
33080
+ const result = /* @__PURE__ */ new Map();
33081
+ if (pubkeys.length === 0) return result;
33082
+ const relays = relayManager.getRelaysForModule("calendar");
33083
+ const events = await nostrRuntime.querySync(relays, { kinds: [10002], authors: pubkeys });
33084
+ const newest = /* @__PURE__ */ new Map();
33085
+ for (const event of events) {
33086
+ const prev = newest.get(event.pubkey);
33087
+ if (!prev || event.created_at > prev.created_at) newest.set(event.pubkey, event);
33088
+ }
33089
+ for (const [pubkey, event] of newest) {
33090
+ const urls = event.tags.filter((t) => t[0] === "r" && t[1]).map((t) => t[1]);
33091
+ if (urls.length > 0) result.set(pubkey, urls);
33092
+ }
33093
+ return result;
33094
+ }
33095
+ async function getInvitationInboxRelays(pubkey) {
33096
+ const relays = relayManager.getRelaysForModule("calendar");
33097
+ try {
33098
+ const configs = await relayManager.fetchUserRelays(pubkey);
33099
+ const readRelays = configs.filter((c) => c.read).map((c) => c.url);
33100
+ return [.../* @__PURE__ */ new Set([...relays, ...readRelays])];
33101
+ } catch {
33102
+ return relays;
33103
+ }
33104
+ }
32256
33105
  function subscribeToCalendarEvents(params, onEvent, onEose) {
32257
33106
  const relays = relayManager.getRelaysForModule("calendar");
32258
33107
  const filter = {
@@ -32539,6 +33388,42 @@ async function deleteCalendarList(coordinate) {
32539
33388
  const relays = relayManager.getRelaysForModule("calendar");
32540
33389
  await nostrRuntime.publish(relays, signed);
32541
33390
  }
33391
+ async function publishParticipantRemovalEvent({
33392
+ kinds,
33393
+ eventIds = [],
33394
+ coordinates = [],
33395
+ reason = ""
33396
+ }) {
33397
+ const signer = await signerManager.getSigner();
33398
+ const tags = [];
33399
+ for (const id of eventIds) tags.push(["e", id]);
33400
+ for (const coord of coordinates) tags.push(["a", coord]);
33401
+ for (const kind of kinds) tags.push(["k", String(kind)]);
33402
+ const event = {
33403
+ kind: CALENDAR_KINDS.participantRemoval,
33404
+ created_at: Math.floor(Date.now() / 1e3),
33405
+ tags,
33406
+ content: reason
33407
+ };
33408
+ const signed = await signer.signEvent(event);
33409
+ const relays = relayManager.getRelaysForModule("calendar");
33410
+ await nostrRuntime.publish(relays, signed);
33411
+ }
33412
+ async function fetchParticipantRemovals(pubkey, relays = relayManager.getRelaysForModule("calendar")) {
33413
+ const ids = /* @__PURE__ */ new Set();
33414
+ const coordinates = /* @__PURE__ */ new Set();
33415
+ const events = await nostrRuntime.querySync(relays, {
33416
+ kinds: [CALENDAR_KINDS.participantRemoval],
33417
+ authors: [pubkey]
33418
+ });
33419
+ for (const ev of events) {
33420
+ for (const tag of ev.tags) {
33421
+ if (tag[0] === "e" && tag[1]) ids.add(tag[1]);
33422
+ else if (tag[0] === "a" && tag[1]) coordinates.add(tag[1]);
33423
+ }
33424
+ }
33425
+ return { ids, coordinates };
33426
+ }
32542
33427
  async function parseCalendarEvent(event, viewKey) {
32543
33428
  const dTag = event.tags.find((t) => t[0] === "d")?.[1] ?? "";
32544
33429
  const isPrivate = event.kind !== CALENDAR_KINDS.publicEvent;
@@ -32566,7 +33451,10 @@ async function parseCalendarEvent(event, viewKey) {
32566
33451
  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
33452
  const startTzid = tags.find((t) => t[0] === "start_tzid")?.[1];
32568
33453
  const endTzid = tags.find((t) => t[0] === "end_tzid")?.[1];
32569
- const registrationFormRef = tags.find((t) => t[0] === "form")?.[1];
33454
+ const formTag = tags.find((t) => t[0] === "form");
33455
+ const registrationFormRef = formTag?.[1];
33456
+ const registrationFormViewKey = formTag?.[2];
33457
+ const notificationPreference = tags.find((t) => t[0] === "notification")?.[1];
32570
33458
  return {
32571
33459
  id: dTag,
32572
33460
  eventId: event.id,
@@ -32588,13 +33476,16 @@ async function parseCalendarEvent(event, viewKey) {
32588
33476
  startTzid,
32589
33477
  endTzid,
32590
33478
  registrationFormRef,
33479
+ registrationFormViewKey,
33480
+ notificationPreference,
32591
33481
  event
32592
33482
  };
32593
33483
  }
32594
33484
  async function fetchInvitationsSync() {
32595
33485
  const signer = await signerManager.getSigner();
32596
33486
  const pubkey = await signer.getPublicKey();
32597
- const relays = relayManager.getRelaysForModule("calendar");
33487
+ const relays = await getInvitationInboxRelays(pubkey);
33488
+ const removals = await fetchParticipantRemovals(pubkey, relays);
32598
33489
  const wraps = await nostrRuntime.querySync(relays, {
32599
33490
  kinds: [CALENDAR_KINDS.giftWrap, CALENDAR_KINDS.rsvpGiftWrap],
32600
33491
  "#p": [pubkey]
@@ -32602,7 +33493,7 @@ async function fetchInvitationsSync() {
32602
33493
  const seen = /* @__PURE__ */ new Set();
32603
33494
  const out = [];
32604
33495
  for (const wrap of wraps) {
32605
- if (seen.has(wrap.id)) continue;
33496
+ if (seen.has(wrap.id) || removals.ids.has(wrap.id)) continue;
32606
33497
  seen.add(wrap.id);
32607
33498
  const invitation = await extractInvitationFromWrap(wrap);
32608
33499
  if (!invitation) continue;
@@ -32614,6 +33505,15 @@ async function fetchInvitationsSync() {
32614
33505
  }
32615
33506
  return out;
32616
33507
  }
33508
+ async function lookupEventViewKey(eventCoordinate) {
33509
+ const lists = await fetchCalendarLists();
33510
+ for (const list of lists) {
33511
+ for (const ref of list.eventRefs) {
33512
+ if (ref[0] === eventCoordinate && ref[2]) return ref[2];
33513
+ }
33514
+ }
33515
+ return void 0;
33516
+ }
32617
33517
 
32618
33518
  // ../agent/src/services/calendar/booking.ts
32619
33519
  var booking_exports = {};
@@ -32624,6 +33524,122 @@ __export(booking_exports, {
32624
33524
  fetchBookingRequests: () => fetchBookingRequests,
32625
33525
  fetchSchedulingPages: () => fetchSchedulingPages
32626
33526
  });
33527
+
33528
+ // ../agent/src/services/calendar/busyList.ts
33529
+ var MONTH_KEY_RE = /^\d{4}-\d{2}$/;
33530
+ function busyListMonthKey(value) {
33531
+ const d = typeof value === "number" ? new Date(value) : value;
33532
+ const month = String(d.getUTCMonth() + 1).padStart(2, "0");
33533
+ return `${d.getUTCFullYear()}-${month}`;
33534
+ }
33535
+ function busyListMonthKeysForRange(startMs, endMs) {
33536
+ if (!Number.isFinite(startMs) || !Number.isFinite(endMs)) return [];
33537
+ const start = Math.min(startMs, endMs);
33538
+ const end = Math.max(startMs, endMs);
33539
+ const keys = [];
33540
+ const cursor = new Date(
33541
+ Date.UTC(new Date(start).getUTCFullYear(), new Date(start).getUTCMonth(), 1)
33542
+ );
33543
+ const endMonth = new Date(
33544
+ Date.UTC(new Date(end).getUTCFullYear(), new Date(end).getUTCMonth(), 1)
33545
+ );
33546
+ while (cursor.getTime() <= endMonth.getTime()) {
33547
+ keys.push(busyListMonthKey(cursor));
33548
+ cursor.setUTCMonth(cursor.getUTCMonth() + 1);
33549
+ }
33550
+ return keys;
33551
+ }
33552
+ function busyListToTags(list) {
33553
+ const tags = [
33554
+ ["d", list.monthKey],
33555
+ ["t", list.monthKey],
33556
+ ["t", "busy"]
33557
+ ];
33558
+ for (const r of list.ranges) {
33559
+ tags.push(["block", String(Math.floor(r.start / 1e3)), String(Math.floor(r.end / 1e3))]);
33560
+ }
33561
+ return tags;
33562
+ }
33563
+ function parseBusyListEvent(event) {
33564
+ const dTag = event.tags.find((t) => t[0] === "d")?.[1] ?? "";
33565
+ if (!MONTH_KEY_RE.test(dTag)) return null;
33566
+ const ranges = [];
33567
+ for (const tag of event.tags) {
33568
+ if (tag[0] !== "block") continue;
33569
+ const start = Number(tag[1]) * 1e3;
33570
+ const end = Number(tag[2]) * 1e3;
33571
+ if (!Number.isFinite(start) || !Number.isFinite(end) || end <= start) continue;
33572
+ ranges.push({ start, end });
33573
+ }
33574
+ ranges.sort((a, b) => a.start - b.start || a.end - b.end);
33575
+ const deduped = [];
33576
+ for (const r of ranges) {
33577
+ const last = deduped[deduped.length - 1];
33578
+ if (last && last.start === r.start && last.end === r.end) continue;
33579
+ deduped.push(r);
33580
+ }
33581
+ return {
33582
+ user: event.pubkey,
33583
+ monthKey: dTag,
33584
+ ranges: deduped,
33585
+ eventId: event.id,
33586
+ createdAt: event.created_at
33587
+ };
33588
+ }
33589
+ async function fetchBusyListsForUser(pubkey, monthKeys) {
33590
+ if (monthKeys.length === 0) return [];
33591
+ const relays = relayManager.getRelaysForModule("calendar");
33592
+ const events = await nostrRuntime.querySync(relays, {
33593
+ kinds: [CALENDAR_KINDS.publicBusyList],
33594
+ authors: [pubkey],
33595
+ "#d": monthKeys
33596
+ });
33597
+ const newestPerMonth = /* @__PURE__ */ new Map();
33598
+ for (const event of events) {
33599
+ const list = parseBusyListEvent(event);
33600
+ if (!list) continue;
33601
+ const prev = newestPerMonth.get(list.monthKey);
33602
+ if (!prev || list.createdAt > prev.createdAt) newestPerMonth.set(list.monthKey, list);
33603
+ }
33604
+ return [...newestPerMonth.values()];
33605
+ }
33606
+ async function publishBusyList(list) {
33607
+ const signer = await signerManager.getSigner();
33608
+ const event = {
33609
+ kind: CALENDAR_KINDS.publicBusyList,
33610
+ created_at: Math.floor(Date.now() / 1e3),
33611
+ tags: busyListToTags(list),
33612
+ content: ""
33613
+ };
33614
+ const signed = await signer.signEvent(event);
33615
+ const relays = relayManager.getRelaysForModule("calendar");
33616
+ await nostrRuntime.publish(relays, signed);
33617
+ }
33618
+ var sameRange = (a, b) => a.start === b.start && a.end === b.end;
33619
+ async function addBusyRange(range) {
33620
+ const signer = await signerManager.getSigner();
33621
+ const pubkey = await signer.getPublicKey();
33622
+ const monthKeys = busyListMonthKeysForRange(range.start, range.end);
33623
+ const existing = new Map(
33624
+ (await fetchBusyListsForUser(pubkey, monthKeys)).map((l) => [l.monthKey, l])
33625
+ );
33626
+ for (const monthKey of monthKeys) {
33627
+ const list = existing.get(monthKey) ?? {
33628
+ user: pubkey,
33629
+ monthKey,
33630
+ ranges: [],
33631
+ eventId: "",
33632
+ createdAt: 0
33633
+ };
33634
+ if (list.ranges.some((r) => sameRange(r, range))) continue;
33635
+ const ranges = [...list.ranges, { start: range.start, end: range.end }].sort(
33636
+ (a, b) => a.start - b.start || a.end - b.end
33637
+ );
33638
+ await publishBusyList({ ...list, ranges });
33639
+ }
33640
+ }
33641
+
33642
+ // ../agent/src/services/calendar/booking.ts
32627
33643
  var BOOKING_HOST = "https://calendar.formstr.app";
32628
33644
  async function fetchSchedulingPages() {
32629
33645
  const signer = await signerManager.getSigner();
@@ -32782,6 +33798,10 @@ async function approveBookingRequest(request, calendar) {
32782
33798
  event.viewKey ?? request.viewKey ?? ""
32783
33799
  ];
32784
33800
  const updatedCalendar = await addEventToCalendarList(calendar, eventRef);
33801
+ try {
33802
+ await addBusyRange({ start: request.start, end: request.end });
33803
+ } catch {
33804
+ }
32785
33805
  await sendBookingResponse({
32786
33806
  schedulingPageRef: request.schedulingPageRef,
32787
33807
  bookerPubkey: request.bookerPubkey,
@@ -32809,7 +33829,9 @@ var service_exports3 = {};
32809
33829
  __export(service_exports3, {
32810
33830
  addSharedPage: () => addSharedPage,
32811
33831
  deletePage: () => deletePage,
33832
+ fetchAllDocMetadata: () => fetchAllDocMetadata,
32812
33833
  fetchDeletions: () => fetchDeletions2,
33834
+ fetchDocMetadata: () => fetchDocMetadata,
32813
33835
  fetchDocTags: () => fetchDocTags,
32814
33836
  fetchMyPages: () => fetchMyPages,
32815
33837
  fetchPage: () => fetchPage,
@@ -32817,17 +33839,22 @@ __export(service_exports3, {
32817
33839
  fetchSharedPages: () => fetchSharedPages,
32818
33840
  generateShareLink: () => generateShareLink,
32819
33841
  isPageDeleted: () => isPageDeleted,
33842
+ saveDocMetadata: () => saveDocMetadata,
32820
33843
  savePage: () => savePage,
32821
- saveSharedList: () => saveSharedList,
33844
+ setDocSharedAs: () => setDocSharedAs,
32822
33845
  setDocTags: () => setDocTags,
33846
+ setDocTitle: () => setDocTitle,
32823
33847
  sharePage: () => sharePage
32824
33848
  });
32825
33849
 
32826
33850
  // ../agent/src/services/pages/types.ts
32827
33851
  var PAGES_KINDS = {
32828
33852
  document: 33457,
33853
+ /** Legacy super-app shared-with-me list — read-only migration source; upstream
33854
+ * (nostr-docs) has no such kind. Shares now live in kind-34579 doc metadata. */
32829
33855
  sharedPagesList: 11234,
32830
33856
  docMetadata: 34579,
33857
+ comment: 1494,
32831
33858
  crdtOp: 22457
32832
33859
  };
32833
33860
 
@@ -32894,6 +33921,7 @@ async function fetchMyPages(viewKeys) {
32894
33921
  authors: [pubkey]
32895
33922
  });
32896
33923
  const deletions = await fetchDeletions2(relays, [pubkey]);
33924
+ const metadata = await fetchAllDocMetadata().catch(() => /* @__PURE__ */ new Map());
32897
33925
  const newest = /* @__PURE__ */ new Map();
32898
33926
  for (const ev of events) {
32899
33927
  if (isPageDeleted(ev, deletions)) continue;
@@ -32904,15 +33932,18 @@ async function fetchMyPages(viewKeys) {
32904
33932
  const summaries = [];
32905
33933
  for (const [dTag, ev] of newest) {
32906
33934
  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 {
33935
+ const meta = metadata.get(address);
33936
+ const viewKey = meta?.viewKey ?? viewKeys?.get(address);
33937
+ let title = meta?.title || `Document ${dTag}`;
33938
+ if (!meta?.title) {
33939
+ try {
33940
+ title = extractTitle(await nip44SelfDecrypt(signer, ev.content));
33941
+ } catch {
33942
+ if (viewKey) {
33943
+ try {
33944
+ title = extractTitle(await decryptWithViewKey2(viewKey, ev.content));
33945
+ } catch {
33946
+ }
32916
33947
  }
32917
33948
  }
32918
33949
  }
@@ -32923,7 +33954,9 @@ async function fetchMyPages(viewKeys) {
32923
33954
  pubkey: ev.pubkey,
32924
33955
  createdAt: ev.created_at,
32925
33956
  isEncrypted: ev.content.length > 0,
32926
- viewKey: viewKeys?.get(address)
33957
+ viewKey,
33958
+ ...Array.isArray(meta?.tags) && meta.tags.length > 0 ? { tags: meta.tags } : {},
33959
+ ...meta?.sharedAs ? { sharedAs: meta.sharedAs } : {}
32927
33960
  });
32928
33961
  }
32929
33962
  return summaries;
@@ -32943,6 +33976,17 @@ async function fetchPage(pubkey, docId, viewKey) {
32943
33976
  decrypted = true;
32944
33977
  } catch {
32945
33978
  }
33979
+ if (!decrypted && !viewKey) {
33980
+ try {
33981
+ const meta = await fetchDocMetadata(`${PAGES_KINDS.document}:${pubkey}:${docId}`);
33982
+ if (meta?.viewKey) {
33983
+ content = await decryptWithViewKey2(meta.viewKey, event.content);
33984
+ viewKey = meta.viewKey;
33985
+ decrypted = true;
33986
+ }
33987
+ } catch {
33988
+ }
33989
+ }
32946
33990
  return {
32947
33991
  id: docId,
32948
33992
  address: `${PAGES_KINDS.document}:${event.pubkey}:${docId}`,
@@ -32957,12 +34001,24 @@ async function fetchPage(pubkey, docId, viewKey) {
32957
34001
  }
32958
34002
  async function deletePage(address) {
32959
34003
  const signer = await signerManager.getSigner();
34004
+ const [kindStr, pubkey, dTag] = address.split(":");
34005
+ let versionIds = [];
34006
+ try {
34007
+ const versions = await nostrRuntime.querySync(RELAYS(), {
34008
+ kinds: [Number(kindStr)],
34009
+ authors: [pubkey],
34010
+ "#d": [dTag]
34011
+ });
34012
+ versionIds = versions.map((v) => v.id);
34013
+ } catch {
34014
+ }
32960
34015
  const event = {
32961
34016
  kind: 5,
32962
34017
  created_at: Math.floor(Date.now() / 1e3),
32963
34018
  tags: [
32964
34019
  ["a", address],
32965
- ["k", String(PAGES_KINDS.document)]
34020
+ ["k", String(PAGES_KINDS.document)],
34021
+ ...versionIds.map((id) => ["e", id])
32966
34022
  ],
32967
34023
  content: "Deleted via Formstr"
32968
34024
  };
@@ -33003,9 +34059,16 @@ function generateShareLink(address, viewKey, editKey) {
33003
34059
  }
33004
34060
  async function sharePage(params) {
33005
34061
  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;
34062
+ const [, ownerPubkey, dTag] = params.address.split(":");
34063
+ const originalMeta = await fetchDocMetadata(params.address).catch(() => void 0);
34064
+ if (params.canEdit && originalMeta?.sharedAs) {
34065
+ const sharedMeta = await fetchDocMetadata(originalMeta.sharedAs).catch(() => void 0);
34066
+ if (sharedMeta?.viewKey && sharedMeta.editKey) {
34067
+ return generateShareLink(originalMeta.sharedAs, sharedMeta.viewKey, sharedMeta.editKey);
34068
+ }
34069
+ }
34070
+ const viewKeyHex = params.viewKey ?? originalMeta?.viewKey ?? generateViewKey2().hex;
34071
+ const editKeyHex = params.canEdit ? params.editKey ?? originalMeta?.editKey ?? generateViewKey2().hex : void 0;
33009
34072
  const encrypted = await encryptWithViewKey2(viewKeyHex, params.content);
33010
34073
  const template = {
33011
34074
  kind: PAGES_KINDS.document,
@@ -33016,9 +34079,36 @@ async function sharePage(params) {
33016
34079
  const signed = editKeyHex ? finalizeEvent(template, hexToBytes4(editKeyHex)) : await signer.signEvent(template);
33017
34080
  await nostrRuntime.publish(RELAYS(), signed);
33018
34081
  const newAddress = `${PAGES_KINDS.document}:${signed.pubkey}:${dTag}`;
34082
+ await saveDocMetadata(newAddress, {
34083
+ viewKey: viewKeyHex,
34084
+ ...editKeyHex ? { editKey: editKeyHex } : {}
34085
+ });
34086
+ if (editKeyHex && ownerPubkey === await signer.getPublicKey()) {
34087
+ await saveDocMetadata(params.address, { sharedAs: newAddress });
34088
+ }
33019
34089
  return generateShareLink(newAddress, viewKeyHex, editKeyHex);
33020
34090
  }
33021
34091
  async function fetchSharedList() {
34092
+ const metadata = await fetchAllDocMetadata();
34093
+ const entries = [];
34094
+ for (const [address, meta] of metadata) {
34095
+ if (typeof meta.viewKey !== "string" || !meta.viewKey) continue;
34096
+ entries.push(
34097
+ typeof meta.editKey === "string" && meta.editKey ? [address, meta.viewKey, meta.editKey] : [address, meta.viewKey]
34098
+ );
34099
+ }
34100
+ for (const entry of await fetchLegacySharedList()) {
34101
+ const [address, viewKey, editKey] = entry;
34102
+ if (metadata.get(address)?.viewKey) continue;
34103
+ entries.push(entry);
34104
+ try {
34105
+ await saveDocMetadata(address, { viewKey, ...editKey ? { editKey } : {} });
34106
+ } catch {
34107
+ }
34108
+ }
34109
+ return entries;
34110
+ }
34111
+ async function fetchLegacySharedList() {
33022
34112
  const signer = await signerManager.getSigner();
33023
34113
  const pubkey = await signer.getPublicKey();
33024
34114
  const events = await nostrRuntime.querySync(RELAYS(), {
@@ -33034,30 +34124,19 @@ async function fetchSharedList() {
33034
34124
  }
33035
34125
  return [];
33036
34126
  }
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
34127
  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;
34128
+ const [address, viewKey, editKey] = entry;
34129
+ await saveDocMetadata(address, { viewKey, ...editKey ? { editKey } : {} });
33054
34130
  }
33055
34131
  async function fetchSharedPages() {
34132
+ const signer = await signerManager.getSigner();
34133
+ const userPubkey = await signer.getPublicKey();
33056
34134
  const entries = await fetchSharedList();
33057
34135
  const out = [];
33058
34136
  for (const [address, viewKey, editKey] of entries) {
33059
34137
  const [, pubkey, dTag] = address.split(":");
33060
34138
  if (!pubkey || !dTag) continue;
34139
+ if (pubkey === userPubkey) continue;
33061
34140
  const doc = await fetchPage(pubkey, dTag, viewKey);
33062
34141
  if (!doc) continue;
33063
34142
  out.push({
@@ -33074,15 +34153,19 @@ async function fetchSharedPages() {
33074
34153
  }
33075
34154
  return out;
33076
34155
  }
33077
- async function fetchDocTags(addresses) {
33078
- const result = /* @__PURE__ */ new Map();
33079
- if (addresses.length === 0) return result;
34156
+ function parseMetadataJson(json) {
34157
+ const parsed = JSON.parse(json);
34158
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
34159
+ return parsed;
34160
+ }
34161
+ return void 0;
34162
+ }
34163
+ async function fetchAllDocMetadata() {
33080
34164
  const signer = await signerManager.getSigner();
33081
34165
  const pubkey = await signer.getPublicKey();
33082
34166
  const events = await nostrRuntime.querySync(RELAYS(), {
33083
34167
  kinds: [PAGES_KINDS.docMetadata],
33084
- authors: [pubkey],
33085
- "#d": addresses
34168
+ authors: [pubkey]
33086
34169
  });
33087
34170
  const newest = /* @__PURE__ */ new Map();
33088
34171
  for (const ev of events) {
@@ -33090,18 +34173,37 @@ async function fetchDocTags(addresses) {
33090
34173
  const prev = newest.get(addr);
33091
34174
  if (!prev || ev.created_at > prev.created_at) newest.set(addr, ev);
33092
34175
  }
34176
+ const result = /* @__PURE__ */ new Map();
33093
34177
  for (const [addr, ev] of newest) {
33094
34178
  try {
33095
- const parsed = JSON.parse(await nip44SelfDecrypt(signer, ev.content));
33096
- if (Array.isArray(parsed.tags)) result.set(addr, parsed.tags);
34179
+ const meta = parseMetadataJson(await nip44SelfDecrypt(signer, ev.content));
34180
+ if (meta) result.set(addr, meta);
33097
34181
  } catch {
33098
34182
  }
33099
34183
  }
33100
34184
  return result;
33101
34185
  }
33102
- async function setDocTags(address, tags) {
34186
+ async function fetchDocMetadata(address) {
34187
+ const signer = await signerManager.getSigner();
34188
+ const pubkey = await signer.getPublicKey();
34189
+ const events = await nostrRuntime.querySync(RELAYS(), {
34190
+ kinds: [PAGES_KINDS.docMetadata],
34191
+ authors: [pubkey],
34192
+ "#d": [address]
34193
+ });
34194
+ if (events.length === 0) return void 0;
34195
+ const newest = events.reduce((a, b) => b.created_at > a.created_at ? b : a);
34196
+ try {
34197
+ return parseMetadataJson(await nip44SelfDecrypt(signer, newest.content));
34198
+ } catch {
34199
+ return void 0;
34200
+ }
34201
+ }
34202
+ async function saveDocMetadata(address, patch) {
34203
+ const existing = await fetchDocMetadata(address) ?? {};
34204
+ const merged = { ...existing, ...patch };
33103
34205
  const signer = await signerManager.getSigner();
33104
- const content = await nip44SelfEncrypt(signer, JSON.stringify({ tags }));
34206
+ const content = await nip44SelfEncrypt(signer, JSON.stringify(merged));
33105
34207
  const signed = await signer.signEvent({
33106
34208
  kind: PAGES_KINDS.docMetadata,
33107
34209
  created_at: Math.floor(Date.now() / 1e3),
@@ -33109,6 +34211,41 @@ async function setDocTags(address, tags) {
33109
34211
  content
33110
34212
  });
33111
34213
  await nostrRuntime.publish(RELAYS(), signed);
34214
+ return merged;
34215
+ }
34216
+ async function fetchDocTags(addresses) {
34217
+ const result = /* @__PURE__ */ new Map();
34218
+ if (addresses.length === 0) return result;
34219
+ const signer = await signerManager.getSigner();
34220
+ const pubkey = await signer.getPublicKey();
34221
+ const events = await nostrRuntime.querySync(RELAYS(), {
34222
+ kinds: [PAGES_KINDS.docMetadata],
34223
+ authors: [pubkey],
34224
+ "#d": addresses
34225
+ });
34226
+ const newest = /* @__PURE__ */ new Map();
34227
+ for (const ev of events) {
34228
+ const addr = ev.tags.find((t) => t[0] === "d")?.[1] ?? "";
34229
+ const prev = newest.get(addr);
34230
+ if (!prev || ev.created_at > prev.created_at) newest.set(addr, ev);
34231
+ }
34232
+ for (const [addr, ev] of newest) {
34233
+ try {
34234
+ const meta = parseMetadataJson(await nip44SelfDecrypt(signer, ev.content));
34235
+ if (meta && Array.isArray(meta.tags)) result.set(addr, meta.tags);
34236
+ } catch {
34237
+ }
34238
+ }
34239
+ return result;
34240
+ }
34241
+ async function setDocTags(address, tags) {
34242
+ await saveDocMetadata(address, { tags });
34243
+ }
34244
+ async function setDocTitle(address, title) {
34245
+ await saveDocMetadata(address, { title: title || void 0 });
34246
+ }
34247
+ async function setDocSharedAs(address, sharedAs) {
34248
+ await saveDocMetadata(address, { sharedAs });
33112
34249
  }
33113
34250
  function extractTitle(markdown) {
33114
34251
  const firstLine = markdown.trim().split("\n")[0] ?? "";
@@ -33123,11 +34260,82 @@ function randomId(length) {
33123
34260
  return result;
33124
34261
  }
33125
34262
 
34263
+ // ../agent/src/services/pages/comments.ts
34264
+ var comments_exports = {};
34265
+ __export(comments_exports, {
34266
+ fetchPageComments: () => fetchPageComments,
34267
+ parsePageComment: () => parsePageComment,
34268
+ publishPageComment: () => publishPageComment
34269
+ });
34270
+ var RELAYS2 = () => relayManager.getRelaysForModule("pages");
34271
+ async function publishPageComment(draft, viewKey, docAddress, docEventId) {
34272
+ const signer = await signerManager.getSigner();
34273
+ const innerTags = [
34274
+ ["content", draft.content],
34275
+ ["type", draft.type]
34276
+ ];
34277
+ if (draft.quote !== void 0) innerTags.push(["quote", draft.quote]);
34278
+ if (draft.context !== void 0) {
34279
+ innerTags.push(["context", draft.context.prefix, draft.context.suffix]);
34280
+ }
34281
+ const docOwnerPubkey = docAddress.split(":")[1];
34282
+ const event = {
34283
+ kind: PAGES_KINDS.comment,
34284
+ created_at: Math.floor(Date.now() / 1e3),
34285
+ content: await encryptWithViewKey2(viewKey, JSON.stringify(innerTags)),
34286
+ tags: [
34287
+ ["a", docAddress],
34288
+ ["e", docEventId],
34289
+ ["p", docOwnerPubkey]
34290
+ ]
34291
+ };
34292
+ const signed = await signer.signEvent(event);
34293
+ await nostrRuntime.publish(RELAYS2(), signed);
34294
+ return signed;
34295
+ }
34296
+ async function parsePageComment(event, viewKey) {
34297
+ try {
34298
+ const inner = JSON.parse(await decryptWithViewKey2(viewKey, event.content));
34299
+ if (!Array.isArray(inner)) return void 0;
34300
+ const rows = inner;
34301
+ const find = (name) => rows.find((r) => Array.isArray(r) && r[0] === name);
34302
+ const content = find("content")?.[1];
34303
+ if (typeof content !== "string") return void 0;
34304
+ const type = find("type")?.[1] === "suggestion" ? "suggestion" : "comment";
34305
+ const quote = find("quote")?.[1];
34306
+ const contextRow = find("context");
34307
+ return {
34308
+ id: event.id,
34309
+ author: event.pubkey,
34310
+ createdAt: event.created_at,
34311
+ content,
34312
+ type,
34313
+ ...quote !== void 0 ? { quote } : {},
34314
+ ...contextRow ? { context: { prefix: contextRow[1] ?? "", suffix: contextRow[2] ?? "" } } : {}
34315
+ };
34316
+ } catch {
34317
+ return void 0;
34318
+ }
34319
+ }
34320
+ async function fetchPageComments(docAddress, viewKey) {
34321
+ const events = await nostrRuntime.querySync(RELAYS2(), {
34322
+ kinds: [PAGES_KINDS.comment],
34323
+ "#a": [docAddress]
34324
+ });
34325
+ const out = [];
34326
+ for (const event of events) {
34327
+ const comment = await parsePageComment(event, viewKey);
34328
+ if (comment) out.push(comment);
34329
+ }
34330
+ return out.sort((a, b) => a.createdAt - b.createdAt);
34331
+ }
34332
+
33126
34333
  // ../agent/src/services/drive/service.ts
33127
34334
  var service_exports4 = {};
33128
34335
  __export(service_exports4, {
33129
34336
  deleteFile: () => deleteFile,
33130
34337
  downloadFile: () => downloadFile,
34338
+ downloadPreview: () => downloadPreview,
33131
34339
  extractFolders: () => extractFolders,
33132
34340
  fetchBlossomServers: () => fetchBlossomServers,
33133
34341
  fetchFileIndex: () => fetchFileIndex,
@@ -33143,8 +34351,8 @@ var DRIVE_KINDS = {
33143
34351
  fileMetadata: 34578
33144
34352
  };
33145
34353
  var DEFAULT_BLOSSOM_SERVERS = [
33146
- "https://blossom.primal.net",
33147
34354
  "https://nostr.download",
34355
+ "https://blossom.primal.net",
33148
34356
  "https://blossom.oxtr.dev"
33149
34357
  ];
33150
34358
 
@@ -33159,6 +34367,21 @@ async function uploadFile(params) {
33159
34367
  const authEvent = await createBlossomAuthEvent("upload", await sha256Hex2(encryptedBytes), signer);
33160
34368
  const blossom = new BlossomClient(server);
33161
34369
  const result = await blossom.upload(encryptedBytes, authEvent, params.file.type);
34370
+ let previewHash;
34371
+ if (params.preview) {
34372
+ try {
34373
+ const encryptedPreview = new TextEncoder().encode(
34374
+ await encryptFileWithExistingKey(params.preview, privateKeyHex)
34375
+ );
34376
+ const previewAuth = await createBlossomAuthEvent(
34377
+ "upload",
34378
+ await sha256Hex2(encryptedPreview),
34379
+ signer
34380
+ );
34381
+ previewHash = (await blossom.upload(encryptedPreview, previewAuth, "image/webp")).sha256;
34382
+ } catch {
34383
+ }
34384
+ }
33162
34385
  const metadata = {
33163
34386
  name: params.file.name,
33164
34387
  hash: result.sha256,
@@ -33167,11 +34390,26 @@ async function uploadFile(params) {
33167
34390
  folder: params.folder ?? "/",
33168
34391
  uploadedAt: Date.now(),
33169
34392
  server,
33170
- encryptionKey: privateKeyHex
34393
+ ...previewHash ? { previewHash } : {},
34394
+ encryptionKey: privateKeyHex,
34395
+ encryptionAlgorithm: "aes-gcm"
33171
34396
  };
33172
34397
  await saveFileMetadata(metadata);
33173
34398
  return metadata;
33174
34399
  }
34400
+ async function downloadPreview(metadata) {
34401
+ if (!metadata.previewHash) return null;
34402
+ const blossom = new BlossomClient(metadata.server);
34403
+ let authEvent;
34404
+ try {
34405
+ const signer = await signerManager.getSigner();
34406
+ authEvent = await createBlossomAuthEvent("get", metadata.previewHash, signer);
34407
+ } catch {
34408
+ }
34409
+ const encryptedBytes = await blossom.download(metadata.previewHash, authEvent);
34410
+ const ciphertext = new TextDecoder().decode(encryptedBytes);
34411
+ return decryptFileWithKey(ciphertext, metadata.encryptionKey);
34412
+ }
33175
34413
  async function downloadFile(metadata) {
33176
34414
  const blossom = new BlossomClient(metadata.server);
33177
34415
  let authEvent;
@@ -33215,7 +34453,11 @@ async function fetchFileIndex() {
33215
34453
  }
33216
34454
  async function saveFileMetadata(metadata) {
33217
34455
  const signer = await signerManager.getSigner();
33218
- const encrypted = await nip44SelfEncrypt(signer, JSON.stringify(metadata));
34456
+ const full = {
34457
+ ...metadata,
34458
+ encryptionAlgorithm: metadata.encryptionAlgorithm ?? "aes-gcm"
34459
+ };
34460
+ const encrypted = await nip44SelfEncrypt(signer, JSON.stringify(full));
33219
34461
  const event = {
33220
34462
  kind: DRIVE_KINDS.fileMetadata,
33221
34463
  created_at: Math.floor(Date.now() / 1e3),
@@ -33311,6 +34553,28 @@ __export(service_exports5, {
33311
34553
  submitPollResponse: () => submitPollResponse
33312
34554
  });
33313
34555
 
34556
+ // ../agent/src/services/polls/pow.ts
34557
+ function minePollEvent(unsigned, difficulty) {
34558
+ const event = unsigned;
34559
+ const nonceTag = ["nonce", "0", difficulty.toString()];
34560
+ event.tags.push(nonceTag);
34561
+ event.tags.push(["W", difficulty.toString()]);
34562
+ let count = 0;
34563
+ for (; ; ) {
34564
+ const now2 = Math.floor(Date.now() / 1e3);
34565
+ if (now2 !== event.created_at) {
34566
+ count = 0;
34567
+ event.created_at = now2;
34568
+ }
34569
+ nonceTag[1] = (++count).toString();
34570
+ event.id = getEventHash(event);
34571
+ if (nip13_exports.getPow(event.id) >= difficulty) return event;
34572
+ }
34573
+ }
34574
+ function hasValidPow(event, difficulty) {
34575
+ return nip13_exports.getPow(event.id) >= difficulty;
34576
+ }
34577
+
33314
34578
  // ../agent/src/services/polls/types.ts
33315
34579
  var POLLS_KINDS = {
33316
34580
  poll: 1068,
@@ -33365,7 +34629,7 @@ async function createPoll(draft) {
33365
34629
  function withModuleRelays(pollRelays) {
33366
34630
  return Array.from(/* @__PURE__ */ new Set([...pollRelays ?? [], ...relayManager.getRelaysForModule("polls")]));
33367
34631
  }
33368
- async function submitPollResponse(pollId, pollAuthor, selectedOptionIds, pollRelays) {
34632
+ async function submitPollResponse(pollId, pollAuthor, selectedOptionIds, pollRelays, powDifficulty) {
33369
34633
  const signer = await signerManager.getSigner();
33370
34634
  const relays = withModuleRelays(pollRelays);
33371
34635
  const tags = [
@@ -33375,12 +34639,22 @@ async function submitPollResponse(pollId, pollAuthor, selectedOptionIds, pollRel
33375
34639
  for (const optionId of selectedOptionIds) {
33376
34640
  tags.push(["response", optionId]);
33377
34641
  }
33378
- const event = {
34642
+ let event = {
33379
34643
  kind: POLLS_KINDS.response,
33380
34644
  created_at: Math.floor(Date.now() / 1e3),
33381
34645
  tags,
33382
34646
  content: ""
33383
34647
  };
34648
+ if (powDifficulty && powDifficulty > 0) {
34649
+ const unsigned = { ...event, pubkey: await signer.getPublicKey() };
34650
+ const mined = minePollEvent(unsigned, powDifficulty);
34651
+ event = {
34652
+ kind: mined.kind,
34653
+ created_at: mined.created_at,
34654
+ tags: mined.tags,
34655
+ content: mined.content
34656
+ };
34657
+ }
33384
34658
  const signed = await signer.signEvent(event);
33385
34659
  await nostrRuntime.publish(relays, signed);
33386
34660
  }
@@ -33428,11 +34702,12 @@ async function fetchPoll(eventId) {
33428
34702
  function resultRelays(poll) {
33429
34703
  return withModuleRelays(poll.relays);
33430
34704
  }
33431
- function latestResponsesByVoter(events, deleted) {
34705
+ function latestResponsesByVoter(events, deleted, powDifficulty = 0) {
33432
34706
  const latest = /* @__PURE__ */ new Map();
33433
34707
  for (const e of events) {
33434
34708
  if (isPollDeleted(e, deleted)) continue;
33435
34709
  if (!e.tags.some((t) => t[0] === "response")) continue;
34710
+ if (powDifficulty > 0 && !hasValidPow(e, powDifficulty)) continue;
33436
34711
  const prev = latest.get(e.pubkey);
33437
34712
  if (!prev || e.created_at > prev.created_at) latest.set(e.pubkey, e);
33438
34713
  }
@@ -33450,11 +34725,13 @@ async function fetchPollResults(poll) {
33450
34725
  const events = await nostrRuntime.querySync(relays, {
33451
34726
  kinds: [POLLS_KINDS.response, POLLS_KINDS.responseLegacy],
33452
34727
  "#e": [poll.id],
33453
- ...poll.endsAt ? { until: poll.endsAt } : {}
34728
+ ...poll.endsAt ? { until: poll.endsAt } : {},
34729
+ // PoW polls: upstream queries votes by their ["W", difficulty] tag.
34730
+ ...poll.powDifficulty ? { "#W": [String(poll.powDifficulty)] } : {}
33454
34731
  });
33455
34732
  const authors = Array.from(new Set(events.map((e) => e.pubkey)));
33456
34733
  const deleted = await fetchDeletions3(relays, authors);
33457
- return computeResults(latestResponsesByVoter(events, deleted));
34734
+ return computeResults(latestResponsesByVoter(events, deleted, poll.powDifficulty ?? 0));
33458
34735
  }
33459
34736
  async function fetchRecentPolls(limit = 20) {
33460
34737
  const relays = relayManager.getRelaysForModule("polls");
@@ -33523,7 +34800,7 @@ function computeResults(responses) {
33523
34800
  for (const [pubkey, selected] of responses) {
33524
34801
  for (const optionId of selected) {
33525
34802
  const existing = counts.get(optionId) ?? [];
33526
- existing.push(pubkey);
34803
+ if (!existing.includes(pubkey)) existing.push(pubkey);
33527
34804
  counts.set(optionId, existing);
33528
34805
  }
33529
34806
  }
@@ -33816,11 +35093,12 @@ function buildCalendarTools() {
33816
35093
  server.registerTool(
33817
35094
  "rsvp_event",
33818
35095
  {
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.",
35096
+ 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
35097
  inputSchema: {
33821
35098
  eventCoordinate: external_exports.string(),
33822
35099
  status: external_exports.enum(["accepted", "declined", "tentative"]),
33823
35100
  isPrivate: external_exports.boolean().optional(),
35101
+ viewKey: external_exports.string().optional(),
33824
35102
  suggestedStart: external_exports.number().optional(),
33825
35103
  suggestedEnd: external_exports.number().optional(),
33826
35104
  comment: external_exports.string().optional(),
@@ -33831,6 +35109,7 @@ function buildCalendarTools() {
33831
35109
  eventCoordinate,
33832
35110
  status,
33833
35111
  isPrivate,
35112
+ viewKey,
33834
35113
  suggestedStart,
33835
35114
  suggestedEnd,
33836
35115
  comment,
@@ -33838,16 +35117,13 @@ function buildCalendarTools() {
33838
35117
  }) => {
33839
35118
  const blocked = requireConfirm("rsvp_event", { confirm }, `sends "${status}" RSVP`);
33840
35119
  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));
35120
+ let key = viewKey;
35121
+ if (isPrivate && !key) {
35122
+ key = await service_exports2.lookupEventViewKey(eventCoordinate);
33850
35123
  }
35124
+ const hasExtra = suggestedStart !== void 0 || suggestedEnd !== void 0 || comment !== void 0;
35125
+ const extra = hasExtra ? { suggestedStart, suggestedEnd, comment } : void 0;
35126
+ await rsvp_exports.rsvpToEvent(eventCoordinate, status, Boolean(isPrivate), extra, key);
33851
35127
  return ok(`RSVP "${status}" sent.`);
33852
35128
  }
33853
35129
  );
@@ -33887,6 +35163,9 @@ function buildCalendarTools() {
33887
35163
  rrule: args.rrule ?? existing.repeat.rrule ?? void 0,
33888
35164
  startTzid: args.startTzid ?? existing.startTzid,
33889
35165
  registrationFormRef: existing.registrationFormRef,
35166
+ registrationFormViewKey: existing.registrationFormViewKey,
35167
+ notificationPreference: existing.notificationPreference,
35168
+ viewKey: existing.viewKey,
33890
35169
  existingId: existing.id
33891
35170
  };
33892
35171
  const event = existing.isPrivate ? await service_exports2.publishPrivateCalendarEvent(draft, existing.calendarId ?? "default") : await service_exports2.publishPublicCalendarEvent(draft);
@@ -33926,6 +35205,8 @@ function buildCalendarTools() {
33926
35205
  rrule: existing.repeat.rrule ?? void 0,
33927
35206
  startTzid: existing.startTzid,
33928
35207
  registrationFormRef: formRef,
35208
+ notificationPreference: existing.notificationPreference,
35209
+ viewKey: existing.viewKey,
33929
35210
  existingId: existing.id
33930
35211
  };
33931
35212
  const event = existing.isPrivate ? await service_exports2.publishPrivateCalendarEvent(draft, existing.calendarId ?? "default") : await service_exports2.publishPublicCalendarEvent(draft);
@@ -34208,8 +35489,13 @@ function normalizePubkeyList(values) {
34208
35489
  return values.map((v) => typeof v === "string" ? normalizePubkey(v) : null).filter((p) => !!p);
34209
35490
  }
34210
35491
  var ANSWER_TYPES = new Set(Object.values(AnswerType));
35492
+ var LEGACY_TYPE_ALIASES = {
35493
+ multiChoiceGrid: "multipleChoiceGrid" /* multipleChoiceGrid */
35494
+ };
34211
35495
  function coerceType(type) {
34212
- return type && ANSWER_TYPES.has(type) ? type : "shortText" /* shortText */;
35496
+ if (!type) return "shortText" /* shortText */;
35497
+ if (LEGACY_TYPE_ALIASES[type]) return LEGACY_TYPE_ALIASES[type];
35498
+ return ANSWER_TYPES.has(type) ? type : "shortText" /* shortText */;
34213
35499
  }
34214
35500
  function normalizeOptions(options) {
34215
35501
  if (!options) return void 0;
@@ -34229,7 +35515,8 @@ function aiFieldsToFormFields(value) {
34229
35515
  validation: f.validation,
34230
35516
  gridRows: f.gridRows,
34231
35517
  gridCols: f.gridCols,
34232
- fileConfig: f.fileConfig
35518
+ fileConfig: f.fileConfig,
35519
+ maxStars: f.maxStars
34233
35520
  }));
34234
35521
  }
34235
35522
 
@@ -34253,6 +35540,7 @@ var fieldShape = external_exports.object({
34253
35540
  }).optional(),
34254
35541
  gridRows: external_exports.array(external_exports.string()).optional(),
34255
35542
  gridCols: external_exports.array(external_exports.string()).optional(),
35543
+ maxStars: external_exports.number().optional(),
34256
35544
  fileConfig: external_exports.object({
34257
35545
  blossomServer: external_exports.string().optional(),
34258
35546
  maxBytes: external_exports.number().optional(),
@@ -34351,7 +35639,13 @@ ${form.settings.description}`);
34351
35639
  const signingKey = mine.find(
34352
35640
  (f) => f.pubkey === formAuthorPubkey && f.id === formId
34353
35641
  )?.signingKey;
34354
- const responses = await service_exports.fetchResponses(formAuthorPubkey, formId, signingKey);
35642
+ const template = await service_exports.fetchForm(formAuthorPubkey, formId);
35643
+ const responses = await service_exports.fetchResponses(
35644
+ formAuthorPubkey,
35645
+ formId,
35646
+ signingKey,
35647
+ template?.relays
35648
+ );
34355
35649
  const blocks = responses.map((r) => {
34356
35650
  const when = new Date(r.createdAt * 1e3).toISOString();
34357
35651
  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 +35760,32 @@ To let collaborators decrypt it, run \`share_form\` with their npubs.` : ""),
34466
35760
  },
34467
35761
  {
34468
35762
  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.",
35763
+ 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
35764
  inputSchema: {
34471
35765
  formId: external_exports.string(),
34472
35766
  formPubkey: external_exports.string(),
34473
35767
  recipients: external_exports.array(external_exports.string()),
35768
+ editors: external_exports.array(external_exports.string()).optional(),
34474
35769
  confirm: external_exports.boolean().optional()
34475
35770
  },
34476
35771
  write: true,
34477
- handler: async ({ formId, formPubkey, recipients, confirm }) => {
35772
+ handler: async ({ formId, formPubkey, recipients, editors, confirm }) => {
34478
35773
  const recipientHex = normalizePubkeyList(recipients);
34479
- if (recipientHex.length === 0) return fail("No valid recipients provided.", "BAD_INPUT");
35774
+ const editorHex = normalizePubkeyList(editors ?? []);
35775
+ if (recipientHex.length === 0 && editorHex.length === 0)
35776
+ return fail("No valid recipients provided.", "BAD_INPUT");
34480
35777
  const blocked = requireConfirm(
34481
35778
  "share_form",
34482
35779
  { confirm },
34483
- `gift-wraps the view key of form ${formId} to ${recipientHex.length} recipient(s)`
35780
+ `gift-wraps the access keys of form ${formId} to ${recipientHex.length + editorHex.length} recipient(s)`
34484
35781
  );
34485
35782
  if (blocked) return blocked;
34486
- const result = await service_exports.shareForm({ formId, formPubkey, recipients: recipientHex });
35783
+ const result = await service_exports.shareForm({
35784
+ formId,
35785
+ formPubkey,
35786
+ recipients: recipientHex,
35787
+ editors: editorHex
35788
+ });
34487
35789
  const failedNote = result.failed.length ? ` (${result.failed.length} failed)` : "";
34488
35790
  return ok(`Shared view key with ${result.published} recipient(s)${failedNote}.`, {
34489
35791
  published: result.published,
@@ -34527,6 +35829,7 @@ To let collaborators decrypt it, run \`share_form\` with their npubs.` : ""),
34527
35829
  `publicly submits ${answers.length} answer(s) to form ${formId}`
34528
35830
  );
34529
35831
  if (blocked) return blocked;
35832
+ const template = await service_exports.fetchForm(formAuthorPubkey, formId);
34530
35833
  await service_exports.submitResponse(
34531
35834
  formAuthorPubkey,
34532
35835
  formId,
@@ -34535,7 +35838,9 @@ To let collaborators decrypt it, run \`share_form\` with their npubs.` : ""),
34535
35838
  answer: a.answer,
34536
35839
  metadata: a.metadata
34537
35840
  })),
34538
- Boolean(encrypt7)
35841
+ Boolean(encrypt7),
35842
+ void 0,
35843
+ template?.relays
34539
35844
  );
34540
35845
  return ok(`Submitted ${answers.length} answer(s) to form ${formId}.`);
34541
35846
  }
@@ -34590,7 +35895,7 @@ function buildPagesTools() {
34590
35895
  server.registerTool(
34591
35896
  "list_shared_pages",
34592
35897
  {
34593
- description: "List documents others have shared with you (kind-11234 inbox).",
35898
+ description: "List documents others have shared with you (kind-34579 doc-metadata entries carrying a viewKey).",
34594
35899
  inputSchema: {}
34595
35900
  },
34596
35901
  async () => {
@@ -34615,6 +35920,25 @@ function buildPagesTools() {
34615
35920
  return ok("Tags fetched.", { address, tags: map.get(address) ?? [] });
34616
35921
  }
34617
35922
  );
35923
+ server.registerTool(
35924
+ "list_page_comments",
35925
+ {
35926
+ description: "List the inline comments/suggestions on a shared document (kind 1494; needs the doc's viewKey).",
35927
+ inputSchema: { address: external_exports.string(), viewKey: external_exports.string() }
35928
+ },
35929
+ async ({ address, viewKey }) => {
35930
+ const comments = await comments_exports.fetchPageComments(address, viewKey);
35931
+ return ok(`${comments.length} comment(s).`, {
35932
+ comments: comments.map((c) => ({
35933
+ author: c.author,
35934
+ createdAt: c.createdAt,
35935
+ type: c.type,
35936
+ content: c.content,
35937
+ quote: c.quote
35938
+ }))
35939
+ });
35940
+ }
35941
+ );
34618
35942
  server.registerTool(
34619
35943
  "create_page",
34620
35944
  {
@@ -34721,6 +36045,44 @@ function buildPagesTools() {
34721
36045
  });
34722
36046
  }
34723
36047
  );
36048
+ server.registerTool(
36049
+ "add_page_comment",
36050
+ {
36051
+ description: "Add an inline comment or suggestion to a shared document (kind 1494). Requires confirm:true.",
36052
+ inputSchema: {
36053
+ address: external_exports.string(),
36054
+ eventId: external_exports.string(),
36055
+ viewKey: external_exports.string(),
36056
+ content: external_exports.string(),
36057
+ type: external_exports.enum(["comment", "suggestion"]).optional(),
36058
+ quote: external_exports.string().optional(),
36059
+ confirm: external_exports.boolean().optional()
36060
+ }
36061
+ },
36062
+ async ({
36063
+ address,
36064
+ eventId,
36065
+ viewKey,
36066
+ content,
36067
+ type,
36068
+ quote,
36069
+ confirm
36070
+ }) => {
36071
+ const blocked = requireConfirm(
36072
+ "add_page_comment",
36073
+ { confirm },
36074
+ `publishes a comment on ${address}`
36075
+ );
36076
+ if (blocked) return blocked;
36077
+ const event = await comments_exports.publishPageComment(
36078
+ { content, type: type ?? "comment", ...quote !== void 0 ? { quote } : {} },
36079
+ viewKey,
36080
+ address,
36081
+ eventId
36082
+ );
36083
+ return ok("Comment published.", { id: event.id });
36084
+ }
36085
+ );
34724
36086
  return tools;
34725
36087
  }
34726
36088
 
@@ -34857,7 +36219,13 @@ function buildPollsTools() {
34857
36219
  if (blocked) return blocked;
34858
36220
  const poll = await service_exports5.fetchPoll(pollEventId);
34859
36221
  if (!poll) return fail("Poll not found.");
34860
- await service_exports5.submitPollResponse(pollEventId, poll.pubkey, optionIds, poll.relays);
36222
+ await service_exports5.submitPollResponse(
36223
+ pollEventId,
36224
+ poll.pubkey,
36225
+ optionIds,
36226
+ poll.relays,
36227
+ poll.powDifficulty
36228
+ );
34861
36229
  return ok(`Voted on poll ${pollEventId}.`);
34862
36230
  }
34863
36231
  );
@@ -45086,7 +46454,7 @@ function adapt(r) {
45086
46454
  };
45087
46455
  }
45088
46456
  function buildServer(ctx) {
45089
- const server = new McpServer({ name: "formstr", version: "0.1.1" });
46457
+ const server = new McpServer({ name: "formstr", version: "0.1.0" });
45090
46458
  for (const t of toolRegistry) {
45091
46459
  if (t.write && !ctx.allowWrites) continue;
45092
46460
  server.registerTool(
@@ -45106,7 +46474,17 @@ async function startStdio(ctx) {
45106
46474
  // src/index.ts
45107
46475
  async function runServer(cli) {
45108
46476
  const cfg = resolveConfig(cli, process.env);
45109
- const account = await bootstrap({ account: cfg.account, relays: cfg.relays });
46477
+ const deps = process.stdin.isTTY ? {
46478
+ promptPassphrase: async (question) => {
46479
+ const io = createTerminalIo();
46480
+ try {
46481
+ return await io.promptPassphrase(question);
46482
+ } finally {
46483
+ io.close();
46484
+ }
46485
+ }
46486
+ } : {};
46487
+ const account = await bootstrap({ account: cfg.account, relays: cfg.relays }, deps);
45110
46488
  console.error(
45111
46489
  `formstr-mcp: signed in as ${account.npub} (${account.method}), writes ${cfg.allowWrites ? "ENABLED" : "disabled"}`
45112
46490
  );
@@ -45115,13 +46493,14 @@ async function runServer(cli) {
45115
46493
  }
45116
46494
  async function runLogin(cli) {
45117
46495
  const io = createTerminalIo();
46496
+ const pool = createPatchedPool();
45118
46497
  try {
45119
46498
  const account = await doLogin({
45120
46499
  signer: await buildMcpSigner(),
45121
46500
  prompt: io.prompt,
45122
46501
  promptPassphrase: io.promptPassphrase,
45123
46502
  printQr,
45124
- pool: createPatchedPool(),
46503
+ pool,
45125
46504
  relays: cli.relays
45126
46505
  });
45127
46506
  console.error(
@@ -45129,6 +46508,10 @@ async function runLogin(cli) {
45129
46508
  );
45130
46509
  } finally {
45131
46510
  io.close();
46511
+ try {
46512
+ pool.destroy();
46513
+ } catch {
46514
+ }
45132
46515
  }
45133
46516
  }
45134
46517
  async function main() {
@@ -45136,18 +46519,18 @@ async function main() {
45136
46519
  switch (cli.command) {
45137
46520
  case "login":
45138
46521
  await runLogin(cli);
45139
- return;
46522
+ return cli.command;
45140
46523
  case "logout": {
45141
46524
  await doLogout(await buildMcpSigner(), cli.account);
45142
46525
  console.error("formstr-mcp: signed out.");
45143
- return;
46526
+ return cli.command;
45144
46527
  }
45145
46528
  case "whoami": {
45146
46529
  const who = whoami(await buildMcpSigner());
45147
46530
  console.error(
45148
46531
  who ? `formstr-mcp: ${who.npub} (${who.method})` : "formstr-mcp: not signed in."
45149
46532
  );
45150
- return;
46533
+ return cli.command;
45151
46534
  }
45152
46535
  case "accounts": {
45153
46536
  const signer = await buildMcpSigner();
@@ -45155,26 +46538,45 @@ async function main() {
45155
46538
  const accounts = listAccounts(signer);
45156
46539
  if (accounts.length === 0) {
45157
46540
  console.error("formstr-mcp: no accounts. Run `formstr-mcp login`.");
45158
- return;
46541
+ return cli.command;
45159
46542
  }
45160
46543
  for (const a of accounts) {
45161
46544
  const marker = active && a.pubkey === active.pubkey ? "* " : " ";
45162
46545
  console.error(`${marker}${a.npub} (${a.method})`);
45163
46546
  }
45164
- return;
46547
+ return cli.command;
46548
+ }
46549
+ case "switch": {
46550
+ if (!cli.target) {
46551
+ throw new Error(
46552
+ "Usage: formstr-mcp switch <npub|hex>. Run `formstr-mcp accounts` to list them."
46553
+ );
46554
+ }
46555
+ const account = await doSwitch(await buildMcpSigner(), cli.target);
46556
+ console.error(`formstr-mcp: active account is now ${account.npub} (${account.method}).`);
46557
+ return cli.command;
45165
46558
  }
46559
+ case "help":
46560
+ console.error(helpText());
46561
+ return cli.command;
45166
46562
  case "run":
45167
46563
  default:
45168
46564
  await runServer(cli);
46565
+ return "run";
45169
46566
  }
45170
46567
  }
45171
- main().catch((err) => {
45172
- console.error("formstr-mcp: fatal:", err instanceof Error ? err.message : err);
46568
+ main().then((command) => {
46569
+ if (command !== "run") process.exit(0);
46570
+ }).catch((err) => {
46571
+ const debug = !!(process.env.FORMSTR_MCP_DEBUG || process.env.DEBUG);
46572
+ console.error("formstr-mcp: fatal:", formatFatal(err, debug));
46573
+ if (!debug) console.error("formstr-mcp: (set FORMSTR_MCP_DEBUG=1 for a full stack trace)");
45173
46574
  process.exit(1);
45174
46575
  });
45175
46576
  /*! Bundled license information:
45176
46577
 
45177
46578
  @noble/hashes/utils.js:
46579
+ @noble/hashes/esm/utils.js:
45178
46580
  (*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
45179
46581
 
45180
46582
  @noble/curves/utils.js: