dexe-mcp 0.5.7 → 0.5.8

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.
@@ -1 +1 @@
1
- {"version":3,"file":"ipfs.d.ts","sourceRoot":"","sources":["../../src/lib/ipfs.ts"],"names":[],"mappings":"AAOA;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAAO,CAAC;AAEzD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,2CAA2C;IAC3C,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,UAAU,CAAC;IAClB,gEAAgE;IAChE,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IACrB,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAsB,SAAS,CAC7B,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,eAAe,GACnB,OAAO,CAAC,eAAe,CAAC,CAwC1B;AAID,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,8FAA8F;IAC9F,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAoB/C;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,iEAAiE;AACjE,wBAAsB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAIhE;AAED,iEAAiE;AACjE,wBAAsB,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAGpE;AAyBD,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,YAAY;IACX,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,EAAE,MAAM;IAIxC,4DAA4D;IACtD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAOrB,OAAO,CACX,OAAO,EAAE,OAAO,EAChB,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,GAC3D,OAAO,CAAC,eAAe,CAAC;IA0BrB,OAAO,CACX,KAAK,EAAE,UAAU,EACjB,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAChE,OAAO,CAAC,eAAe,CAAC;CAwB5B"}
1
+ {"version":3,"file":"ipfs.d.ts","sourceRoot":"","sources":["../../src/lib/ipfs.ts"],"names":[],"mappings":"AAOA;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAAO,CAAC;AAEzD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,2CAA2C;IAC3C,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,UAAU,CAAC;IAClB,gEAAgE;IAChE,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IACrB,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAsB,SAAS,CAC7B,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,eAAe,GACnB,OAAO,CAAC,eAAe,CAAC,CAkD1B;AAID,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,8FAA8F;IAC9F,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAoB/C;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAK7C;AAED,iEAAiE;AACjE,wBAAsB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAIhE;AAED,iEAAiE;AACjE,wBAAsB,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAGpE;AAyBD,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,YAAY;IACX,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,EAAE,MAAM;IAIxC,4DAA4D;IACtD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAOrB,OAAO,CACX,OAAO,EAAE,OAAO,EAChB,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,GAC3D,OAAO,CAAC,eAAe,CAAC;IA0BrB,OAAO,CACX,KAAK,EAAE,UAAU,EACjB,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,EAAE,OAAO,CAAA;KAAE,GAC7F,OAAO,CAAC,eAAe,CAAC;CAiC5B"}
package/dist/lib/ipfs.js CHANGED
@@ -19,14 +19,24 @@ export async function fetchIpfs(cid, cfg) {
19
19
  const timeout = cfg.perRequestTimeoutMs ?? 4000;
20
20
  const errors = [];
21
21
  let attempts = 0;
22
+ const pinataGatewayToken = process.env.DEXE_PINATA_GATEWAY_TOKEN?.trim();
22
23
  for (const gw of cfg.gateways) {
23
24
  attempts++;
24
25
  const base = gw.replace(/\/+$/, "").replace(/\/ipfs$/, "");
25
26
  const url = `${base}/ipfs/${cidStr}`;
26
27
  const controller = new AbortController();
27
28
  const t = setTimeout(() => controller.abort(), timeout);
29
+ // Pinata "dedicated gateways" (`*.mypinata.cloud`) in Restricted mode
30
+ // reject anonymous GETs with HTTP 403. They authenticate via a separate
31
+ // Gateway Key (NOT the API JWT used for pinning); pass it as either
32
+ // `?pinataGatewayToken=…` or the `x-pinata-gateway-token` header. We use
33
+ // the header form. Public gateways receive no auth header.
34
+ const headers = {};
35
+ if (pinataGatewayToken && /\.mypinata\.cloud(\/|$)/i.test(base)) {
36
+ headers["x-pinata-gateway-token"] = pinataGatewayToken;
37
+ }
28
38
  try {
29
- const res = await fetch(url, { signal: controller.signal });
39
+ const res = await fetch(url, { signal: controller.signal, headers });
30
40
  if (!res.ok) {
31
41
  errors.push(`${gw} → HTTP ${res.status}`);
32
42
  continue;
@@ -77,6 +87,18 @@ export function parseCid(input) {
77
87
  export function stripIpfsPrefix(s) {
78
88
  return s.replace(/^ipfs:\/\//, "").replace(/^\/?ipfs\//, "");
79
89
  }
90
+ /**
91
+ * Convert a CID string to its v1 base32 form (idempotent for v1 inputs).
92
+ * Frontend uses subdomain gateway (`<cid>.ipfs.4everland.io`), which only
93
+ * resolves CID v1 base32. Passing a v0 (Qm...) here produces a dead link.
94
+ */
95
+ export function toCidV1(input) {
96
+ const s = stripIpfsPrefix(input);
97
+ const cid = CID.parse(s);
98
+ if (cid.version === 1)
99
+ return cid.toString(base32);
100
+ return cid.toV1().toString(base32);
101
+ }
80
102
  /** Compute the CIDv1 for arbitrary JSON locally — no network. */
81
103
  export async function cidForJson(value) {
82
104
  const bytes = json.encode(value);
@@ -149,10 +171,19 @@ export class PinataClient {
149
171
  const blob = new Blob([bytes], {
150
172
  type: opts?.contentType ?? "application/octet-stream",
151
173
  });
174
+ // Default: wrap-with-directory so the returned CID is a directory whose
175
+ // single child is `fileName`. That's what lets subdomain gateways serve
176
+ // `<cid>.ipfs.<host>/<fileName>` — without the wrapper, the CID is a raw
177
+ // file and any path suffix returns 404, breaking every consumer that
178
+ // builds URLs from `cid + fileName` (DeXe frontend + ipfs-cache.dexe.io).
179
+ const wrap = opts?.wrapWithDirectory ?? true;
152
180
  form.append("file", blob, opts?.fileName ?? "file");
153
181
  if (opts?.name) {
154
182
  form.append("pinataMetadata", JSON.stringify({ name: opts.name }));
155
183
  }
184
+ if (wrap) {
185
+ form.append("pinataOptions", JSON.stringify({ wrapWithDirectory: true }));
186
+ }
156
187
  const res = await fetch(PINATA_PIN_FILE_URL, {
157
188
  method: "POST",
158
189
  headers: { Authorization: `Bearer ${this.jwt}` },
@@ -1 +1 @@
1
- {"version":3,"file":"ipfs.js","sourceRoot":"","sources":["../../src/lib/ipfs.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ipfs.js","sourceRoot":"","sources":["../../src/lib/ipfs.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ipfs.d.ts","sourceRoot":"","sources":["../../src/tools/ipfs.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EACL,WAAW,EAKZ,MAAM,gBAAgB,CAAC;AAGxB,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,WAAW,GAAG,IAAI,CAS3E;AAuaD,OAAO,EAAE,WAAW,EAAE,CAAC"}
1
+ {"version":3,"file":"ipfs.d.ts","sourceRoot":"","sources":["../../src/tools/ipfs.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EACL,WAAW,EAMZ,MAAM,gBAAgB,CAAC;AAGxB,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,WAAW,GAAG,IAAI,CAY3E;AAmhBD,OAAO,EAAE,WAAW,EAAE,CAAC"}
@@ -1,11 +1,14 @@
1
1
  import { z } from "zod";
2
- import { cidForBytes, cidForJson, fetchIpfs, parseCid, PinataClient, } from "../lib/ipfs.js";
2
+ import { cidForBytes, cidForJson, fetchIpfs, parseCid, PinataClient, toCidV1, } from "../lib/ipfs.js";
3
3
  import { markdownToSlate } from "../lib/markdownToSlate.js";
4
4
  export function registerIpfsTools(server, ctx) {
5
5
  const gateways = resolveGateways(ctx);
6
6
  registerUploadProposalMetadata(server, ctx);
7
7
  registerUploadDaoMetadata(server, ctx);
8
8
  registerUploadFile(server, ctx);
9
+ registerUploadAvatar(server, ctx);
10
+ registerGenerateAvatar(server, ctx);
11
+ registerUpdateDaoMetadata(server, ctx, gateways);
9
12
  registerFetch(server, gateways);
10
13
  registerCidInfo(server, gateways);
11
14
  registerCidForJson(server);
@@ -19,18 +22,90 @@ function errorResult(message) {
19
22
  * the user opts in via `DEXE_IPFS_GATEWAYS_FALLBACK`. Returns an empty array
20
23
  * if nothing is configured; the fetch tool fails clean in that case.
21
24
  */
25
+ /**
26
+ * Subdomain-gateway host used to build the `avatarUrl` field stored inside
27
+ * DAO metadata. Must speak the `<cidV1>.ipfs.<host>/<filename>` schema so the
28
+ * DeXe frontend's `parseAvatarFromIpfsResponse` can round-trip the URL.
29
+ *
30
+ * Default is `dweb.link`. The frontend historically hardcoded `4everland.io`,
31
+ * but 4everland fails to discover freshly-pinned CIDs for tens of minutes —
32
+ * during that window the backend cache (`ipfs-cache.dexe.io`) can't fetch
33
+ * the avatar and serves 404 for `<CID>.jpeg`, leaving the profile image
34
+ * broken on app.dexe.io. dweb.link / w3s.link / gateway.pinata.cloud all
35
+ * resolve the same pins immediately.
36
+ *
37
+ * Configurable via `DEXE_IPFS_AVATAR_GATEWAY` (host, no scheme).
38
+ */
39
+ function avatarSubdomainHost() {
40
+ const override = process.env.DEXE_IPFS_AVATAR_GATEWAY?.trim().replace(/^https?:\/\//i, "").replace(/\/$/, "");
41
+ return override || "dweb.link";
42
+ }
43
+ function buildAvatarUrl(cidV1, fileName) {
44
+ return `https://${cidV1}.ipfs.${avatarSubdomainHost()}/${fileName}`;
45
+ }
46
+ /**
47
+ * Best-effort POST to the DeXe IPFS cache service so the next reader hit on
48
+ * `https://ipfs-cache.dexe.io/<cid>.json|.jpeg` serves cached bytes instead
49
+ * of 404. The frontend's modify-profile flow does this automatically; the
50
+ * MCP path didn't, which is why agents who landed a `editDescriptionURL`
51
+ * proposal saw their new avatar fail to render on app.dexe.io even though
52
+ * the on-chain pointer was correct.
53
+ *
54
+ * Never throws; returns the warmed CID(s) on success.
55
+ */
56
+ async function warmDexeIpfsCache(cidV0) {
57
+ try {
58
+ const body = JSON.stringify({ data: { attributes: { link: cidV0 } } });
59
+ const r = await fetch("https://api.dexe.io/integrations/ipfs-cache-svc/public/pool-info", {
60
+ method: "POST",
61
+ headers: { "Content-Type": "application/json" },
62
+ body,
63
+ });
64
+ return { ok: r.ok, status: r.status };
65
+ }
66
+ catch (err) {
67
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
68
+ }
69
+ }
22
70
  function resolveGateways(_ctx) {
23
71
  const out = [];
24
- const primary = process.env.DEXE_IPFS_GATEWAY?.trim();
25
- if (primary)
26
- out.push(primary.replace(/\/$/, ""));
27
- const fallback = process.env.DEXE_IPFS_GATEWAYS_FALLBACK?.trim();
72
+ const normalize = (raw) => {
73
+ const trimmed = raw.trim().replace(/\/$/, "");
74
+ if (!trimmed)
75
+ return "";
76
+ // Allow operators to set DEXE_IPFS_GATEWAY=<host> without a scheme — fetch()
77
+ // refuses such URLs with "Failed to parse URL", which masquerades as an
78
+ // IPFS outage. Default to https since every realistic gateway requires it.
79
+ if (/^https?:\/\//i.test(trimmed))
80
+ return trimmed;
81
+ return `https://${trimmed}`;
82
+ };
83
+ const primary = process.env.DEXE_IPFS_GATEWAY;
84
+ if (primary) {
85
+ const p = normalize(primary);
86
+ if (p)
87
+ out.push(p);
88
+ }
89
+ const fallback = process.env.DEXE_IPFS_GATEWAYS_FALLBACK;
28
90
  if (fallback) {
29
- for (const g of fallback.split(",").map((s) => s.trim().replace(/\/$/, ""))) {
91
+ for (const g of fallback.split(",").map(normalize)) {
30
92
  if (g && !out.includes(g))
31
93
  out.push(g);
32
94
  }
33
95
  }
96
+ // Auto-fallback: if the primary is a Pinata dedicated gateway AND no
97
+ // gateway key is configured, anonymous reads return 403 and tools like
98
+ // dexe_ipfs_update_dao_metadata hang. Append `https://ipfs.io` as a
99
+ // last-resort public reader so flows keep working out of the box.
100
+ // Opt-out via DEXE_IPFS_DISABLE_PUBLIC_FALLBACK=1.
101
+ const disablePublic = process.env.DEXE_IPFS_DISABLE_PUBLIC_FALLBACK === "1";
102
+ const usesRestrictedPinata = out.some((g) => /\.mypinata\.cloud(\/|$)/i.test(g));
103
+ const haveGatewayKey = !!process.env.DEXE_PINATA_GATEWAY_TOKEN?.trim();
104
+ if (!disablePublic && usesRestrictedPinata && !haveGatewayKey) {
105
+ const publicFallback = "https://ipfs.io";
106
+ if (!out.includes(publicFallback))
107
+ out.push(publicFallback);
108
+ }
34
109
  return out;
35
110
  }
36
111
  const NO_GATEWAY_HINT = "No IPFS gateway configured. Set DEXE_IPFS_GATEWAY to a dedicated gateway " +
@@ -110,7 +185,7 @@ function registerUploadProposalMetadata(server, ctx) {
110
185
  *
111
186
  * Outer metadata shape (must match for frontend UI compatibility):
112
187
  * {
113
- * avatarUrl: "https://<cidV1>.ipfs.4everland.io/<filename>" | "",
188
+ * avatarUrl: "https://<cidV1>.ipfs.<host>/<filename>" | "",
114
189
  * avatarCID: "<cidV1>" | undefined,
115
190
  * avatarFileName: "<filename>.jpeg" | "",
116
191
  * daoName: string,
@@ -170,13 +245,15 @@ function registerUploadDaoMetadata(server, ctx) {
170
245
  const descriptionIpfsPath = `ipfs://${descriptionRes.cid}`;
171
246
  // Step 2: Build the outer metadata wrapper (matches frontend schema exactly)
172
247
  let avatarUrl = "";
248
+ let avatarCidV1;
173
249
  if (avatarCID && avatarFileName) {
174
- // Frontend uses 4everland gateway: https://<cidV1>.ipfs.4everland.io/<filename>
175
- avatarUrl = `https://${avatarCID}.ipfs.4everland.io/${avatarFileName}`;
250
+ // Normalize to CID v1 base32 — subdomain gateway only resolves v1.
251
+ avatarCidV1 = toCidV1(avatarCID);
252
+ avatarUrl = buildAvatarUrl(avatarCidV1, avatarFileName);
176
253
  }
177
254
  const outerPayload = {
178
255
  avatarUrl,
179
- avatarCID: avatarCID ?? undefined,
256
+ avatarCID: avatarCidV1,
180
257
  avatarFileName: avatarFileName ?? "",
181
258
  daoName,
182
259
  websiteUrl,
@@ -188,12 +265,16 @@ function registerUploadDaoMetadata(server, ctx) {
188
265
  const metadataRes = await client.pinJson(outerPayload, {
189
266
  name: `dao:${daoName.slice(0, 48)}`,
190
267
  });
268
+ // Step 4: Prewarm the DeXe IPFS cache so app.dexe.io renders the new
269
+ // metadata + avatar immediately. Best-effort; never fails the call.
270
+ const warmed = await warmDexeIpfsCache(metadataRes.cid);
191
271
  const structured = {
192
272
  cid: metadataRes.cid,
193
273
  descriptionCid: descriptionRes.cid,
194
274
  size: metadataRes.size,
195
275
  pinnedAt: metadataRes.pinnedAt,
196
276
  descriptionURL: metadataRes.cid,
277
+ cachePrewarmed: warmed.ok,
197
278
  };
198
279
  return {
199
280
  content: [
@@ -202,6 +283,7 @@ function registerUploadDaoMetadata(server, ctx) {
202
283
  text: `Pinned DAO metadata (frontend-compatible):\n` +
203
284
  ` description content → ${descriptionRes.cid}\n` +
204
285
  ` outer metadata → ${metadataRes.cid} (${metadataRes.size} bytes)\n` +
286
+ ` ipfs-cache.dexe.io prewarm → ${warmed.ok ? "ok" : `skipped (${warmed.status ?? warmed.error})`}\n` +
205
287
  `Use "${metadataRes.cid}" as descriptionURL in deployGovPool.`,
206
288
  },
207
289
  ],
@@ -217,30 +299,53 @@ function registerUploadDaoMetadata(server, ctx) {
217
299
  function registerUploadFile(server, ctx) {
218
300
  server.registerTool("dexe_ipfs_upload_file", {
219
301
  title: "Upload raw bytes (avatar, attachment, etc.) to IPFS (Pinata)",
220
- description: "Pins a file to IPFS. Accepts base64-encoded bytes; returns the CID. Use for DAO avatars or proposal attachments.",
302
+ description: "Pins a file to IPFS. Accepts base64-encoded bytes; returns the CID v1 (base32) and the (possibly normalized) filename. " +
303
+ "For images (contentType: image/*) the filename extension is normalized to `.jpeg` to match what the DeXe frontend stores — " +
304
+ "this is what `dexe_ipfs_upload_dao_metadata` and the DAO profile reader expect. Set `normalizeImageExt: false` to opt out.",
221
305
  inputSchema: {
222
306
  base64: z.string().min(1).describe("Base64-encoded file bytes"),
223
307
  fileName: z.string().default("file"),
224
308
  contentType: z.string().default("application/octet-stream"),
309
+ normalizeImageExt: z
310
+ .boolean()
311
+ .default(true)
312
+ .describe("If true and contentType starts with image/, rename the file extension to .jpeg."),
225
313
  },
226
314
  outputSchema: {
227
- cid: z.string(),
315
+ cid: z.string().describe("CID v1 base32 — use this as avatarCID."),
316
+ cidV0: z.string().describe("Original CID returned by Pinata (usually v0 Qm...). Kept for legacy callers."),
317
+ fileName: z.string().describe("Filename actually pinned (possibly normalized to .jpeg)."),
228
318
  size: z.number(),
229
319
  pinnedAt: z.string(),
230
320
  },
231
- }, async ({ base64, fileName = "file", contentType = "application/octet-stream" }) => {
321
+ }, async ({ base64, fileName = "file", contentType = "application/octet-stream", normalizeImageExt = true, }) => {
232
322
  const client = requirePinata(ctx);
233
323
  if ("error" in client)
234
324
  return errorResult(client.error);
235
325
  try {
326
+ const isImage = contentType.toLowerCase().startsWith("image/");
327
+ const effectiveFileName = normalizeImageExt && isImage
328
+ ? `${fileName.includes(".") ? fileName.substring(0, fileName.lastIndexOf(".")) : fileName}.jpeg`
329
+ : fileName;
236
330
  const bytes = Uint8Array.from(Buffer.from(base64, "base64"));
237
- const res = await client.pinFile(bytes, { fileName, contentType, name: fileName });
238
- const structured = { cid: res.cid, size: res.size, pinnedAt: res.pinnedAt };
331
+ const res = await client.pinFile(bytes, {
332
+ fileName: effectiveFileName,
333
+ contentType,
334
+ name: effectiveFileName,
335
+ });
336
+ const cidV1 = toCidV1(res.cid);
337
+ const structured = {
338
+ cid: cidV1,
339
+ cidV0: res.cid,
340
+ fileName: effectiveFileName,
341
+ size: res.size,
342
+ pinnedAt: res.pinnedAt,
343
+ };
239
344
  return {
240
345
  content: [
241
346
  {
242
347
  type: "text",
243
- text: `Pinned ${fileName} (${bytes.length} bytes) → ${res.cid} (size on IPFS=${res.size})`,
348
+ text: `Pinned ${effectiveFileName} (${bytes.length} bytes) → ${cidV1} (v0=${res.cid}, size on IPFS=${res.size})`,
244
349
  },
245
350
  ],
246
351
  structuredContent: structured,
@@ -373,4 +478,320 @@ function registerCidForJson(server) {
373
478
  // Keep this export so `cidForBytes` isn't flagged as dead code by strict builds;
374
479
  // tools can opt into it later.
375
480
  export { cidForBytes };
481
+ // ---------- dexe_ipfs_upload_avatar (one-shot composite) ----------
482
+ /**
483
+ * Convenience wrapper around `dexe_ipfs_upload_file` that returns the
484
+ * exact triple `dexe_ipfs_upload_dao_metadata` (and `*_modify_dao_profile`)
485
+ * expect: { avatarCID (v1), avatarFileName (.jpeg), avatarUrl }.
486
+ *
487
+ * Single call replaces: pinFile → toV1 → rename → build subdomain URL.
488
+ */
489
+ function registerUploadAvatar(server, ctx) {
490
+ server.registerTool("dexe_ipfs_upload_avatar", {
491
+ title: "Upload a DAO avatar (one-shot: pins + returns avatarCID/avatarFileName/avatarUrl)",
492
+ description: "Uploads an image and returns the {avatarCID, avatarFileName, avatarUrl} triple ready to feed into `dexe_ipfs_upload_dao_metadata` " +
493
+ "(for DAO creation) or `dexe_proposal_build_modify_dao_profile` (for profile updates). " +
494
+ "Normalizes the filename to `.jpeg` (matching the frontend) and returns a CID v1 base32 string that resolves on the subdomain gateway.",
495
+ inputSchema: {
496
+ base64: z.string().min(1).describe("Base64-encoded image bytes"),
497
+ fileName: z.string().default("avatar").describe("Base filename; extension will be normalized to .jpeg"),
498
+ contentType: z
499
+ .string()
500
+ .default("image/jpeg")
501
+ .describe("MIME type; must start with image/. Defaults to image/jpeg."),
502
+ },
503
+ outputSchema: {
504
+ avatarCID: z.string().describe("CID v1 base32 — pass to upload_dao_metadata as avatarCID."),
505
+ avatarFileName: z.string().describe("Filename (always ends with .jpeg)."),
506
+ avatarUrl: z.string().describe("Full subdomain-gateway URL — what the frontend stores verbatim."),
507
+ size: z.number(),
508
+ pinnedAt: z.string(),
509
+ },
510
+ }, async ({ base64, fileName = "avatar", contentType = "image/jpeg" }) => {
511
+ if (!contentType.toLowerCase().startsWith("image/")) {
512
+ return errorResult(`contentType must be image/* (got: ${contentType})`);
513
+ }
514
+ const client = requirePinata(ctx);
515
+ if ("error" in client)
516
+ return errorResult(client.error);
517
+ try {
518
+ const base = fileName.includes(".") ? fileName.substring(0, fileName.lastIndexOf(".")) : fileName;
519
+ const normalized = `${base || "avatar"}.jpeg`;
520
+ const bytes = Uint8Array.from(Buffer.from(base64, "base64"));
521
+ const res = await client.pinFile(bytes, {
522
+ fileName: normalized,
523
+ contentType,
524
+ name: normalized,
525
+ });
526
+ const avatarCID = toCidV1(res.cid);
527
+ const avatarUrl = buildAvatarUrl(avatarCID, normalized);
528
+ const structured = {
529
+ avatarCID,
530
+ avatarFileName: normalized,
531
+ avatarUrl,
532
+ size: res.size,
533
+ pinnedAt: res.pinnedAt,
534
+ };
535
+ return {
536
+ content: [
537
+ {
538
+ type: "text",
539
+ text: `Avatar pinned: ${avatarUrl} (cidV1=${avatarCID}, ${bytes.length} bytes)`,
540
+ },
541
+ ],
542
+ structuredContent: structured,
543
+ };
544
+ }
545
+ catch (err) {
546
+ return errorResult(err instanceof Error ? err.message : String(err));
547
+ }
548
+ });
549
+ }
550
+ // ---------- dexe_dao_generate_avatar (no external provider) ----------
551
+ /**
552
+ * Deterministic placeholder avatar: 1–2 letter initials over a hash-coloured
553
+ * gradient. Pure SVG → uploaded as image/svg+xml then served via the same
554
+ * subdomain gateway frontend uses. No third-party generator required.
555
+ */
556
+ function registerGenerateAvatar(server, ctx) {
557
+ server.registerTool("dexe_dao_generate_avatar", {
558
+ title: "Generate a deterministic placeholder avatar for a DAO",
559
+ description: "Builds an SVG avatar with the DAO's initials over a hash-coloured gradient (no external generator) and pins it to IPFS. " +
560
+ "Returns the same {avatarCID, avatarFileName, avatarUrl} shape as `dexe_ipfs_upload_avatar`, " +
561
+ "ready to feed into `dexe_ipfs_upload_dao_metadata` or `dexe_proposal_build_modify_dao_profile`. " +
562
+ "Same input always produces the same colours (great for re-deploys).",
563
+ inputSchema: {
564
+ daoName: z.string().min(1).describe("DAO name; first 1–2 alphanumeric chars become the avatar initials."),
565
+ size: z.number().int().min(64).max(2048).default(512).describe("SVG viewBox size (square)."),
566
+ },
567
+ outputSchema: {
568
+ avatarCID: z.string(),
569
+ avatarFileName: z.string(),
570
+ avatarUrl: z.string(),
571
+ size: z.number(),
572
+ pinnedAt: z.string(),
573
+ },
574
+ }, async ({ daoName, size = 512 }) => {
575
+ const client = requirePinata(ctx);
576
+ if ("error" in client)
577
+ return errorResult(client.error);
578
+ try {
579
+ const svg = buildIdenticonSvg(daoName, size);
580
+ const bytes = Buffer.from(svg, "utf8");
581
+ // Frontend reader looks for an extension; we keep .jpeg to stay
582
+ // consistent with what `dexe_ipfs_upload_avatar` returns even though
583
+ // the bytes themselves are SVG. The subdomain gateway serves it by
584
+ // CID; content negotiation handles the type.
585
+ const fileName = "avatar.jpeg";
586
+ const res = await client.pinFile(bytes, {
587
+ fileName,
588
+ contentType: "image/svg+xml",
589
+ name: fileName,
590
+ });
591
+ const avatarCID = toCidV1(res.cid);
592
+ const avatarUrl = buildAvatarUrl(avatarCID, fileName);
593
+ const structured = {
594
+ avatarCID,
595
+ avatarFileName: fileName,
596
+ avatarUrl,
597
+ size: res.size,
598
+ pinnedAt: res.pinnedAt,
599
+ };
600
+ return {
601
+ content: [
602
+ {
603
+ type: "text",
604
+ text: `Generated avatar for "${daoName}" → ${avatarUrl}`,
605
+ },
606
+ ],
607
+ structuredContent: structured,
608
+ };
609
+ }
610
+ catch (err) {
611
+ return errorResult(err instanceof Error ? err.message : String(err));
612
+ }
613
+ });
614
+ }
615
+ /** djb2-style hash → unsigned 32-bit. */
616
+ function hashString(s) {
617
+ let h = 5381;
618
+ for (let i = 0; i < s.length; i++)
619
+ h = ((h << 5) + h + s.charCodeAt(i)) >>> 0;
620
+ return h >>> 0;
621
+ }
622
+ function buildIdenticonSvg(daoName, size) {
623
+ const cleaned = daoName.replace(/[^\p{L}\p{N}]/gu, "");
624
+ const initials = (cleaned.slice(0, 2) || "?").toUpperCase();
625
+ const h = hashString(daoName);
626
+ const hue1 = h % 360;
627
+ const hue2 = (hue1 + 40 + ((h >>> 8) % 80)) % 360;
628
+ const c1 = `hsl(${hue1} 70% 55%)`;
629
+ const c2 = `hsl(${hue2} 70% 35%)`;
630
+ const fontSize = Math.round(size * 0.46);
631
+ // Plain SVG — no <foreignObject>, no JS. Safe to pin and serve via subdomain gateway.
632
+ return `<?xml version="1.0" encoding="UTF-8"?>\n<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 ${size} ${size}">\n <defs>\n <linearGradient id="g" x1="0" y1="0" x2="1" y2="1">\n <stop offset="0%" stop-color="${c1}"/>\n <stop offset="100%" stop-color="${c2}"/>\n </linearGradient>\n </defs>\n <rect width="100%" height="100%" fill="url(#g)"/>\n <text x="50%" y="50%" dy=".35em" text-anchor="middle" font-family="Inter, Helvetica, Arial, sans-serif" font-size="${fontSize}" font-weight="700" fill="white">${initials}</text>\n</svg>\n`;
633
+ }
634
+ // ---------- dexe_ipfs_update_dao_metadata (fetch + merge + re-upload) ----------
635
+ /**
636
+ * Smart helper for "Modify DAO Profile" proposals. Fetches the DAO's existing
637
+ * metadata JSON, applies user-supplied partial overrides, re-pins the result.
638
+ *
639
+ * Returns the new outer CID so callers can feed it into
640
+ * `dexe_proposal_build_modify_dao_profile` as `newDescriptionURL`.
641
+ *
642
+ * Without this tool, callers had to re-specify every unchanged field
643
+ * (daoName, websiteUrl, socialLinks, …) on every edit, and any forgotten
644
+ * field would silently disappear from the profile.
645
+ */
646
+ function registerUpdateDaoMetadata(server, ctx, gateways) {
647
+ server.registerTool("dexe_ipfs_update_dao_metadata", {
648
+ title: "Fetch DAO metadata, apply partial overrides, re-upload",
649
+ description: "Reads the existing DAO metadata JSON from IPFS via the configured gateway, applies only the fields you pass in `overrides`, " +
650
+ "and re-pins the merged result. Returns the new outer `descriptionURL` ready for `dexe_proposal_build_modify_dao_profile`. " +
651
+ "Unspecified fields are preserved verbatim (so you can change just the avatar without re-typing the website or social links).",
652
+ inputSchema: {
653
+ currentDescriptionURL: z
654
+ .string()
655
+ .describe("Current DAO descriptionURL — `ipfs://<cid>` or bare CID. Fetched via the configured IPFS gateway."),
656
+ overrides: z
657
+ .object({
658
+ daoName: z.string().optional(),
659
+ websiteUrl: z.string().optional(),
660
+ description: z
661
+ .string()
662
+ .optional()
663
+ .describe("Markdown or plain text. If provided, replaces the description content (re-uploaded as its own pin)."),
664
+ avatarCID: z.string().optional().describe("New avatar CID (any version). Pair with avatarFileName to set, or pass empty string to clear."),
665
+ avatarFileName: z.string().optional(),
666
+ socialLinks: z.array(z.tuple([z.string(), z.string()])).optional(),
667
+ documents: z.array(z.object({ name: z.string(), url: z.string() })).optional(),
668
+ })
669
+ .describe("Only the fields you want to change. Anything omitted is kept from the current metadata."),
670
+ timeoutMs: z.number().int().min(500).max(30_000).default(6000),
671
+ },
672
+ outputSchema: {
673
+ descriptionURL: z.string().describe("New outer CID — pass to dexe_proposal_build_modify_dao_profile.newDescriptionURL."),
674
+ previousDescriptionURL: z.string(),
675
+ cid: z.string(),
676
+ descriptionCid: z.string().optional(),
677
+ size: z.number(),
678
+ pinnedAt: z.string(),
679
+ },
680
+ }, async ({ currentDescriptionURL, overrides, timeoutMs = 6000 }) => {
681
+ if (gateways.length === 0)
682
+ return errorResult(NO_GATEWAY_HINT);
683
+ const client = requirePinata(ctx);
684
+ if ("error" in client)
685
+ return errorResult(client.error);
686
+ try {
687
+ const fetched = await fetchIpfs(currentDescriptionURL, {
688
+ gateways,
689
+ perRequestTimeoutMs: timeoutMs,
690
+ });
691
+ if (!fetched.json || typeof fetched.json !== "object") {
692
+ return errorResult(`Current descriptionURL did not resolve to JSON metadata (content-type=${fetched.contentType}). ` +
693
+ `Got ${fetched.bytes.length} bytes from ${fetched.gateway}.`);
694
+ }
695
+ const current = fetched.json;
696
+ // Per-field merge, with explicit semantics for the trickier ones.
697
+ const daoName = typeof overrides.daoName === "string" ? overrides.daoName : current.daoName ?? "";
698
+ const websiteUrl = typeof overrides.websiteUrl === "string" ? overrides.websiteUrl : current.websiteUrl ?? "";
699
+ const socialLinks = overrides.socialLinks ?? (current.socialLinks ?? []);
700
+ const documents = overrides.documents ?? (current.documents ?? []);
701
+ // Avatar: empty-string avatarCID means "clear". Otherwise normalize to v1.
702
+ let avatarUrl = "";
703
+ let avatarCidV1;
704
+ let avatarFileName = "";
705
+ if (overrides.avatarCID === undefined && overrides.avatarFileName === undefined) {
706
+ // Unchanged — copy avatarCID + filename from current, but rebuild
707
+ // avatarUrl with the configured gateway. Carrying the old URL
708
+ // verbatim leaves stale 4everland references in DAOs that have
709
+ // only had a name/website update; the cache backend then fails
710
+ // to fetch the avatar binary and serves a 404 for `<cid>.jpeg`.
711
+ const currCID = current.avatarCID || "";
712
+ const currFile = current.avatarFileName || "";
713
+ if (currCID && currFile) {
714
+ avatarCidV1 = toCidV1(currCID);
715
+ avatarFileName = currFile;
716
+ avatarUrl = buildAvatarUrl(avatarCidV1, avatarFileName);
717
+ }
718
+ else {
719
+ avatarUrl = current.avatarUrl ?? "";
720
+ avatarCidV1 = currCID || undefined;
721
+ avatarFileName = currFile;
722
+ }
723
+ }
724
+ else if (overrides.avatarCID && overrides.avatarFileName) {
725
+ avatarCidV1 = toCidV1(overrides.avatarCID);
726
+ avatarFileName = overrides.avatarFileName;
727
+ avatarUrl = buildAvatarUrl(avatarCidV1, avatarFileName);
728
+ }
729
+ else if (overrides.avatarCID === "") {
730
+ // Explicit clear.
731
+ avatarUrl = "";
732
+ avatarCidV1 = undefined;
733
+ avatarFileName = "";
734
+ }
735
+ else {
736
+ return errorResult("Avatar overrides require BOTH avatarCID and avatarFileName (or empty string for avatarCID to clear).");
737
+ }
738
+ // Description: re-upload only if changed.
739
+ let descriptionIpfsPath;
740
+ let descriptionCid;
741
+ if (typeof overrides.description === "string") {
742
+ const payload = markdownToSlate(overrides.description);
743
+ const descRes = await client.pinJson(payload, {
744
+ name: `dao-desc:${daoName.slice(0, 40)}`,
745
+ });
746
+ descriptionIpfsPath = `ipfs://${descRes.cid}`;
747
+ descriptionCid = descRes.cid;
748
+ }
749
+ else {
750
+ descriptionIpfsPath = current.description ?? "";
751
+ }
752
+ const outerPayload = {
753
+ avatarUrl,
754
+ avatarCID: avatarCidV1,
755
+ avatarFileName,
756
+ daoName,
757
+ websiteUrl,
758
+ description: descriptionIpfsPath,
759
+ socialLinks,
760
+ documents,
761
+ };
762
+ const metadataRes = await client.pinJson(outerPayload, {
763
+ name: `dao:${daoName.slice(0, 48)}`,
764
+ });
765
+ // Prewarm DeXe ipfs-cache so app.dexe.io renders the new metadata
766
+ // immediately after the modify-profile proposal executes.
767
+ const warmed = await warmDexeIpfsCache(metadataRes.cid);
768
+ const structured = {
769
+ descriptionURL: metadataRes.cid,
770
+ previousDescriptionURL: fetched.cid,
771
+ cid: metadataRes.cid,
772
+ descriptionCid,
773
+ size: metadataRes.size,
774
+ pinnedAt: metadataRes.pinnedAt,
775
+ cachePrewarmed: warmed.ok,
776
+ };
777
+ const changed = Object.keys(overrides).filter((k) => overrides[k] !== undefined);
778
+ return {
779
+ content: [
780
+ {
781
+ type: "text",
782
+ text: `Updated DAO metadata (${changed.length ? changed.join(", ") : "no changes"}):\n` +
783
+ ` previous → ${fetched.cid}\n` +
784
+ ` new → ${metadataRes.cid}\n` +
785
+ ` ipfs-cache.dexe.io prewarm → ${warmed.ok ? "ok" : `skipped (${warmed.status ?? warmed.error})`}\n` +
786
+ `Pass "${metadataRes.cid}" to dexe_proposal_build_modify_dao_profile.newDescriptionURL.`,
787
+ },
788
+ ],
789
+ structuredContent: structured,
790
+ };
791
+ }
792
+ catch (err) {
793
+ return errorResult(err instanceof Error ? err.message : String(err));
794
+ }
795
+ });
796
+ }
376
797
  //# sourceMappingURL=ipfs.js.map