apiblaze 0.21.69 → 0.21.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +473 -191
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -34,7 +34,7 @@ var __toESM = (mod, isNodeMode, target2) => (target2 = mod != null ? __create(__
34
34
  var version;
35
35
  var init_package = __esm({
36
36
  "package.json"() {
37
- version = "0.21.69";
37
+ version = "0.21.70";
38
38
  }
39
39
  });
40
40
 
@@ -5165,6 +5165,141 @@ var init_key = __esm({
5165
5165
  }
5166
5166
  });
5167
5167
 
5168
+ // src/lib/zip.ts
5169
+ function crc32(buf) {
5170
+ let c = 4294967295;
5171
+ for (let i = 0; i < buf.length; i++) c = CRC_TABLE[(c ^ buf[i]) & 255] ^ c >>> 8;
5172
+ return (c ^ 4294967295) >>> 0;
5173
+ }
5174
+ function zipStore(entries2) {
5175
+ const parts = [];
5176
+ const central = [];
5177
+ let offset = 0;
5178
+ const dosTime = 0, dosDate = 1 << 5 | 1;
5179
+ for (const e of entries2) {
5180
+ const name = new TextEncoder().encode(e.path.replace(/^\.\//, ""));
5181
+ const crc = crc32(e.data);
5182
+ const local = new Uint8Array(30 + name.length);
5183
+ const lv = new DataView(local.buffer);
5184
+ lv.setUint32(0, 67324752, true);
5185
+ lv.setUint16(4, 20, true);
5186
+ lv.setUint16(6, 2048, true);
5187
+ lv.setUint16(8, 0, true);
5188
+ lv.setUint16(10, dosTime, true);
5189
+ lv.setUint16(12, dosDate, true);
5190
+ lv.setUint32(14, crc, true);
5191
+ lv.setUint32(18, e.data.length, true);
5192
+ lv.setUint32(22, e.data.length, true);
5193
+ lv.setUint16(26, name.length, true);
5194
+ lv.setUint16(28, 0, true);
5195
+ local.set(name, 30);
5196
+ parts.push(local, e.data);
5197
+ const cd = new Uint8Array(46 + name.length);
5198
+ const cv = new DataView(cd.buffer);
5199
+ cv.setUint32(0, 33639248, true);
5200
+ cv.setUint16(4, 20, true);
5201
+ cv.setUint16(6, 20, true);
5202
+ cv.setUint16(8, 2048, true);
5203
+ cv.setUint16(10, 0, true);
5204
+ cv.setUint16(12, dosTime, true);
5205
+ cv.setUint16(14, dosDate, true);
5206
+ cv.setUint32(16, crc, true);
5207
+ cv.setUint32(20, e.data.length, true);
5208
+ cv.setUint32(24, e.data.length, true);
5209
+ cv.setUint16(28, name.length, true);
5210
+ cv.setUint16(30, 0, true);
5211
+ cv.setUint16(32, 0, true);
5212
+ cv.setUint16(34, 0, true);
5213
+ cv.setUint16(36, 0, true);
5214
+ cv.setUint32(38, 0, true);
5215
+ cv.setUint32(42, offset, true);
5216
+ cd.set(name, 46);
5217
+ central.push(cd);
5218
+ offset += local.length + e.data.length;
5219
+ }
5220
+ const cdSize = central.reduce((n, c) => n + c.length, 0);
5221
+ const end = new Uint8Array(22);
5222
+ const ev = new DataView(end.buffer);
5223
+ ev.setUint32(0, 101010256, true);
5224
+ ev.setUint16(4, 0, true);
5225
+ ev.setUint16(6, 0, true);
5226
+ ev.setUint16(8, entries2.length, true);
5227
+ ev.setUint16(10, entries2.length, true);
5228
+ ev.setUint32(12, cdSize, true);
5229
+ ev.setUint32(16, offset, true);
5230
+ ev.setUint16(20, 0, true);
5231
+ const total = offset + cdSize + end.length;
5232
+ const out = new Uint8Array(total);
5233
+ let p = 0;
5234
+ for (const part of [...parts, ...central, end]) {
5235
+ out.set(part, p);
5236
+ p += part.length;
5237
+ }
5238
+ return out;
5239
+ }
5240
+ function imageInfo(buf) {
5241
+ const dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
5242
+ if (buf.length >= 24 && buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) return { format: "png", width: dv.getUint32(16), height: dv.getUint32(20) };
5243
+ if (buf.length >= 4 && buf[0] === 255 && buf[1] === 216) {
5244
+ let i = 2;
5245
+ while (i + 9 < buf.length) {
5246
+ if (buf[i] !== 255) {
5247
+ i++;
5248
+ continue;
5249
+ }
5250
+ const marker2 = buf[i + 1];
5251
+ if (marker2 === 216 || marker2 === 1 || marker2 >= 208 && marker2 <= 215) {
5252
+ i += 2;
5253
+ continue;
5254
+ }
5255
+ const len = dv.getUint16(i + 2);
5256
+ if (marker2 >= 192 && marker2 <= 207 && marker2 !== 196 && marker2 !== 200 && marker2 !== 204) return { format: "jpeg", height: dv.getUint16(i + 5), width: dv.getUint16(i + 7) };
5257
+ i += 2 + len;
5258
+ }
5259
+ return { format: "jpeg", width: null, height: null };
5260
+ }
5261
+ if (buf.length >= 30 && buf[0] === 82 && buf[1] === 73 && buf[2] === 70 && buf[3] === 70 && buf[8] === 87 && buf[9] === 69 && buf[10] === 66 && buf[11] === 80) {
5262
+ const chunk = String.fromCharCode(buf[12], buf[13], buf[14], buf[15]);
5263
+ if (chunk === "VP8X") return { format: "webp", width: 1 + (buf[24] | buf[25] << 8 | buf[26] << 16), height: 1 + (buf[27] | buf[28] << 8 | buf[29] << 16) };
5264
+ if (chunk === "VP8 ") return { format: "webp", width: dv.getUint16(26, true) & 16383, height: dv.getUint16(28, true) & 16383 };
5265
+ if (chunk === "VP8L") {
5266
+ const b = dv.getUint32(21, true);
5267
+ return { format: "webp", width: (b & 16383) + 1, height: (b >> 14 & 16383) + 1 };
5268
+ }
5269
+ return { format: "webp", width: null, height: null };
5270
+ }
5271
+ const head = new TextDecoder().decode(buf.subarray(0, 512)).trim();
5272
+ if (/^(<\?xml[^>]*>\s*)?(<!--[\s\S]*?-->\s*)*<svg[\s>]/i.test(head)) return { format: "svg", width: null, height: null };
5273
+ return { format: "unknown", width: null, height: null };
5274
+ }
5275
+ function logoProblem(buf) {
5276
+ if (buf.length > 5 * 1024 * 1024) return `the icon is ${(buf.length / 1048576).toFixed(1)} MiB; OpenAI allows 5`;
5277
+ const info = imageInfo(buf);
5278
+ if (info.format === "unknown") return "the icon is not a PNG, JPEG, WebP or SVG";
5279
+ if (info.width != null && info.height != null) {
5280
+ if (info.width !== info.height) return `the icon is ${info.width}\xD7${info.height}; OpenAI wants a square`;
5281
+ if (info.width < 48) return `the icon is ${info.width} px; OpenAI wants at least 48`;
5282
+ if (info.width > 4096) return `the icon is ${info.width} px; OpenAI allows at most 4096`;
5283
+ }
5284
+ return null;
5285
+ }
5286
+ var CRC_TABLE, LOGO_EXT;
5287
+ var init_zip = __esm({
5288
+ "src/lib/zip.ts"() {
5289
+ "use strict";
5290
+ CRC_TABLE = (() => {
5291
+ const t = new Uint32Array(256);
5292
+ for (let n = 0; n < 256; n++) {
5293
+ let c = n;
5294
+ for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
5295
+ t[n] = c >>> 0;
5296
+ }
5297
+ return t;
5298
+ })();
5299
+ LOGO_EXT = { png: "png", jpeg: "jpg", webp: "webp", svg: "svg", unknown: "bin" };
5300
+ }
5301
+ });
5302
+
5168
5303
  // src/lib/directory-probes.ts
5169
5304
  var directory_probes_exports = {};
5170
5305
  __export(directory_probes_exports, {
@@ -5239,7 +5374,9 @@ async function probeDirectoryTarget(target2, deps = {}) {
5239
5374
  }
5240
5375
  })() ? redirected : null,
5241
5376
  sseServed: sse.status == null ? null : sse.status === 200,
5242
- ipv4Reachable: post.status == null && get.status == null ? ipv4 ? null : false : ipv4
5377
+ ipv4Reachable: post.status == null && get.status == null ? ipv4 ? null : false : ipv4,
5378
+ // WP1: a 200 `initialize` says whether the host advertises the MCP Apps UI extension (sign-in hosts answer 401: not probed).
5379
+ uiExtension: post.status === 200 && obj(post.json).result && typeof obj(post.json).result === "object" ? !!obj(obj(post.json).result).capabilities?.extensions?.["io.modelcontextprotocol/ui"] : null
5243
5380
  };
5244
5381
  const prmUrl = `${origin.origin}/.well-known/oauth-protected-resource${origin.pathname.replace(/\/$/, "")}`;
5245
5382
  const prm = await probe(prmUrl, { method: "GET" }, deps);
@@ -5293,6 +5430,39 @@ async function probeDirectoryTarget(target2, deps = {}) {
5293
5430
  if (p.status != null && p.status >= 300 && p.status < 400 && p.location) p = await probe(absolute(p.location, url) ?? p.location, { method: "GET", headers: { accept: "text/html, */*;q=0.5" } }, deps);
5294
5431
  out.metadata = { ...out.metadata ?? {}, [key]: p.status };
5295
5432
  }
5433
+ if (target2.iconUrl && /^https:\/\//i.test(target2.iconUrl)) {
5434
+ const f2 = deps.fetch ?? fetch;
5435
+ const ctl = new AbortController();
5436
+ const timer = setTimeout(() => ctl.abort(), PROBE_TIMEOUT_MS);
5437
+ try {
5438
+ const res = await f2(target2.iconUrl, { redirect: "follow", signal: ctl.signal, headers: { "user-agent": "apiblaze-cli directory-check", accept: "image/*" } });
5439
+ const buf = new Uint8Array(await res.arrayBuffer());
5440
+ const info = res.status === 200 ? imageInfo(buf) : null;
5441
+ out.metadata = { ...out.metadata ?? {}, iconStatus: res.status, iconWidth: info?.width ?? null, iconHeight: info?.height ?? null, iconFormat: info?.format ?? null };
5442
+ } catch {
5443
+ out.metadata = { ...out.metadata ?? {}, iconStatus: null, iconWidth: null, iconHeight: null, iconFormat: null };
5444
+ } finally {
5445
+ clearTimeout(timer);
5446
+ }
5447
+ }
5448
+ if (target2.screenshots?.length) {
5449
+ const f2 = deps.fetch ?? fetch;
5450
+ const shots = await Promise.all(target2.screenshots.slice(0, 5).map(async (url) => {
5451
+ const ctl = new AbortController();
5452
+ const timer = setTimeout(() => ctl.abort(), PROBE_TIMEOUT_MS);
5453
+ try {
5454
+ const res = await f2(url, { redirect: "follow", signal: ctl.signal, headers: { "user-agent": "apiblaze-cli directory-check", accept: "image/*" } });
5455
+ const buf = new Uint8Array(await res.arrayBuffer());
5456
+ const info = res.status === 200 ? imageInfo(buf) : null;
5457
+ return { url, status: res.status, width: info?.width ?? null, height: info?.height ?? null, format: info?.format ?? null };
5458
+ } catch {
5459
+ return { url, status: null, width: null, height: null, format: null };
5460
+ } finally {
5461
+ clearTimeout(timer);
5462
+ }
5463
+ }));
5464
+ out.metadata = { ...out.metadata ?? {}, screenshots: shots };
5465
+ }
5296
5466
  const ch = await probe(`${origin.origin}/.well-known/openai-apps-challenge`, { method: "GET" }, deps);
5297
5467
  out.openaiChallenge = { servedStatus: ch.status, servedMatches: null };
5298
5468
  if (ch.status === 200 && typeof ch.json === "string") out.openaiChallenge.servedBody = ch.json.trim().slice(0, 128);
@@ -5319,6 +5489,7 @@ var init_directory_probes = __esm({
5319
5489
  "src/lib/directory-probes.ts"() {
5320
5490
  "use strict";
5321
5491
  import_promises = __toESM(require("dns/promises"));
5492
+ init_zip();
5322
5493
  PROBE_TIMEOUT_MS = 5e3;
5323
5494
  strArr = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : null;
5324
5495
  obj = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : {};
@@ -5346,7 +5517,8 @@ __export(directory_exports, {
5346
5517
  parseDataHandling: () => parseDataHandling,
5347
5518
  parsePlatforms: () => parsePlatforms,
5348
5519
  parseYesNo: () => parseYesNo,
5349
- printReport: () => printReport
5520
+ printReport: () => printReport,
5521
+ writeJsonLine: () => writeJsonLine
5350
5522
  });
5351
5523
  function attestationId(flag) {
5352
5524
  const f2 = flag.trim().toLowerCase();
@@ -5388,6 +5560,24 @@ function directoryBodyFromFlags(f2) {
5388
5560
  if (f2.tagline) m.tagline = f2.tagline;
5389
5561
  if (f2.description) m.description = f2.description;
5390
5562
  if (f2.iconUrl) m.icon_url = f2.iconUrl;
5563
+ if (f2.slug) m.slug = f2.slug.trim().toLowerCase();
5564
+ if (f2.highRisk !== void 0) {
5565
+ const list = (Array.isArray(f2.highRisk) ? f2.highRisk : String(f2.highRisk).split(",")).map((x) => x.trim().toLowerCase()).filter((x) => x && x !== "none");
5566
+ const bad = list.filter((x) => !["legal", "healthcare", "insurance", "finance"].includes(x));
5567
+ if (bad.length) throw new Error(`--high-risk: unknown "${bad.join(", ")}"; use none, or any of legal, healthcare, insurance, finance (comma-separated).`);
5568
+ m.high_risk = list;
5569
+ }
5570
+ if (f2.mcpApp === true) m.mcp_app = true;
5571
+ if (f2.mcpApp === false) m.mcp_app = null;
5572
+ if (f2.screenshot?.length) {
5573
+ m.screenshots = f2.screenshot.map((x) => {
5574
+ const i = x.indexOf("=");
5575
+ const url = (i > 0 ? x.slice(0, i) : x).trim();
5576
+ const prompt = i > 0 ? x.slice(i + 1).trim() : "";
5577
+ if (!/^https:\/\//i.test(url) || !prompt) throw new Error(`--screenshot wants https://\u2026png="<the prompt that produced it>", got "${x}"`);
5578
+ return { url, prompt };
5579
+ });
5580
+ }
5391
5581
  if (f2.vulnContact) m.vuln_contact = f2.vulnContact;
5392
5582
  if (f2.allowedOrigin?.length) m.allowed_origins = f2.allowedOrigin.map((o) => o.trim()).filter(Boolean);
5393
5583
  if (f2.websiteUrl) m.website_url = f2.websiteUrl.trim();
@@ -5467,7 +5657,7 @@ async function fetchDirectoryReport(caller, proj2, opts = {}) {
5467
5657
  const publishedUrl = opts.publishedUrl ?? first?.report?.project?.publishedUrl ?? null;
5468
5658
  const meta = first?.target?.metadata ?? opts.listing ?? void 0;
5469
5659
  const privacyUrl = opts.privacyUrl ?? meta?.privacyUrl ?? null;
5470
- if (publishedUrl) probes = await probeDirectoryTarget({ publishedUrl, privacyUrl, docsUrl: meta?.docsUrl ?? null, serverName: meta?.serverName ?? null, websiteUrl: meta?.websiteUrl ?? null, supportUrl: meta?.supportUrl ?? null, termsUrl: meta?.termsUrl ?? null });
5660
+ if (publishedUrl) probes = await probeDirectoryTarget({ publishedUrl, privacyUrl, docsUrl: meta?.docsUrl ?? null, serverName: meta?.serverName ?? null, websiteUrl: meta?.websiteUrl ?? null, supportUrl: meta?.supportUrl ?? null, termsUrl: meta?.termsUrl ?? null, screenshots: meta?.mcpApp ? (meta.screenshots ?? []).map((x) => x.url) : null, iconUrl: meta?.iconUrl ?? null });
5471
5661
  else if (first && !opts.refreshSnapshot) return first;
5472
5662
  }
5473
5663
  if (opts.extraProbes) probes = { ...probes ?? {}, ...opts.extraProbes, metadata: { ...probes?.metadata ?? {}, ...opts.extraProbes.metadata ?? {} } };
@@ -5482,9 +5672,12 @@ function isReady(r, platforms) {
5482
5672
  const want = platforms ?? r.report.platformsRequested ?? [...PLATFORMS];
5483
5673
  return want.every((p) => r.report.platforms[p]?.ready === true);
5484
5674
  }
5675
+ function writeJsonLine(value) {
5676
+ import_node_fs.default.writeSync(1, JSON.stringify(value) + "\n");
5677
+ }
5485
5678
  function printReport(r, json) {
5486
5679
  if (json) {
5487
- process.stdout.write(JSON.stringify(r) + "\n");
5680
+ writeJsonLine(r);
5488
5681
  return;
5489
5682
  }
5490
5683
  console.log(r.rendered.text.replace(/<project>/g, r.report.project.id));
@@ -5498,10 +5691,11 @@ function blockerLines(r, platforms) {
5498
5691
  function isApiError(e, status2) {
5499
5692
  return e instanceof ApiError && (status2 === void 0 || e.status === status2);
5500
5693
  }
5501
- var import_chalk17, PLATFORMS, ATTESTATIONS, OPENAI_CATEGORIES, AUTH_MODES, DATA_HANDLING_MODES, reportPath, DESCRIPTION_MIN;
5694
+ var import_node_fs, import_chalk17, PLATFORMS, ATTESTATIONS, OPENAI_CATEGORIES, AUTH_MODES, DATA_HANDLING_MODES, reportPath, DESCRIPTION_MIN;
5502
5695
  var init_directory = __esm({
5503
5696
  "src/lib/directory.ts"() {
5504
5697
  "use strict";
5698
+ import_node_fs = __toESM(require("fs"));
5505
5699
  import_chalk17 = __toESM(require("chalk"));
5506
5700
  init_caller();
5507
5701
  init_directory_probes();
@@ -5519,6 +5713,8 @@ var init_directory = __esm({
5519
5713
  // ChatGPT's guidelines add these three.
5520
5714
  { id: "no_digital_goods_sales", flags: ["no-digital-goods", "no-digital-goods-sales", "no_digital_goods_sales"], label: "No sale of digital goods, subscriptions or credits; no upgrade/checkout flows (ChatGPT)", platforms: ["chatgpt"] },
5521
5715
  { id: "content_13_no_ads", flags: ["content-13-no-ads", "content-13", "content_13_no_ads"], label: "Content suitable for 13+ and no advertising (ChatGPT)", platforms: ["chatgpt"] },
5716
+ // WP5 (Claude-lane review A11): both directories want the policy to say what is collected, used, shared and kept, and how to reach you.
5717
+ { id: "privacy_policy_covers", flags: ["privacy-policy-covers", "privacy_policy_covers", "privacy-covers"], label: "The privacy policy covers data collection, use, sharing, retention and a contact, and says tool traffic passes through APIblaze as a processor", platforms: PLATFORMS },
5522
5718
  { id: "no_restricted_data", flags: ["no-restricted-data", "no_restricted_data"], label: "No card data, health records, government ids or credentials are collected (ChatGPT)", platforms: ["chatgpt"] }
5523
5719
  ];
5524
5720
  OPENAI_CATEGORIES = ["Productivity", "Creativity", "Developer Tools", "Business & Operations", "Data & Analytics", "Communication", "Education & Research", "Security", "Finance", "Healthcare", "Travel", "Entertainment", "Other"];
@@ -7618,7 +7814,8 @@ __export(portal_drafts_exports, {
7618
7814
  draftDescription: () => draftDescription,
7619
7815
  draftMissing: () => draftMissing,
7620
7816
  draftReviewerInstructions: () => draftReviewerInstructions,
7621
- draftUseCases: () => draftUseCases
7817
+ draftUseCases: () => draftUseCases,
7818
+ humanizeShape: () => humanizeShape
7622
7819
  });
7623
7820
  function describeResult(text) {
7624
7821
  const t = (text ?? "").trim();
@@ -7635,6 +7832,21 @@ function describeResult(text) {
7635
7832
  const head = t.replace(/\s+/g, " ").slice(0, 80);
7636
7833
  return `a text answer ("${head}${t.length > 80 ? "\u2026" : ""}")`;
7637
7834
  }
7835
+ function humanKey(k) {
7836
+ const words = k.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").toLowerCase().trim();
7837
+ if (/^(is|has) /.test(words)) return `whether it ${words}`;
7838
+ if (/ (at|on)$/.test(words)) return `when it was ${words.replace(/ (at|on)$/, "")}`;
7839
+ if (words === "id") return "an id";
7840
+ if (words === "url") return "the URL";
7841
+ return words;
7842
+ }
7843
+ function humanizeShape(shape) {
7844
+ const m = /^a (?:JSON answer|list of (\d+) items?) with (.+?)(?: \(\+\d+ more\))?$/.exec(shape.trim());
7845
+ if (!m) return shape.replace(/ \(\+\d+ more\)/, "");
7846
+ const keys = m[2].split(",").map((x) => x.trim()).filter(Boolean).slice(0, 6).map(humanKey);
7847
+ const list = keys.length > 1 ? `${keys.slice(0, -1).join(", ")} and ${keys[keys.length - 1]}` : keys[0] ?? "an answer";
7848
+ return m[1] ? `a list of ${m[1]} item${m[1] === "1" ? "" : "s"}, each with ${list}` : `${list}`;
7849
+ }
7638
7850
  function argsText(args) {
7639
7851
  if (!args || !Object.keys(args).length) return "no arguments";
7640
7852
  return Object.entries(args).map(([k, v]) => `${k}=${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
@@ -7660,7 +7872,7 @@ function draftUseCases(i) {
7660
7872
  for (const g of groups.values()) {
7661
7873
  const row = i.rows.find((r) => r.tool === g.tools[0].name && r.valid === "ok");
7662
7874
  const which = g.prompts.length === list.length && list.length > 1 ? "Each of these" : g.prompts.map(short).join(", ");
7663
- parts.push(`${which} make${g.prompts.length === 1 ? "s" : ""} the assistant call ${g.tools.map(titleOf).join(" and ")}${row ? `, which returns ${shapeOf(row)}` : ""}.`);
7875
+ parts.push(`${which} answer${g.prompts.length === 1 ? "s" : ""} with ${row ? humanizeShape(shapeOf(row)) : `what ${g.tools.map(titleOf).join(" and ")} gives`}.`);
7664
7876
  }
7665
7877
  const uncalled = i.tools.filter((t) => !Object.values(i.promptTools).some((ns) => ns.includes(t.name)));
7666
7878
  if (uncalled.length) parts.push(`Also available: ${uncalled.map((t) => `${titleOf(t)}${(t.description ?? "").trim() ? ` (${(t.description ?? "").trim().replace(/\.$/, "")})` : ""}`).join("; ")}.`);
@@ -7671,19 +7883,19 @@ function draftReviewerInstructions(i) {
7671
7883
  const lines = [];
7672
7884
  const where = i.platforms?.length === 1 && i.platforms[0] === "chatgpt" ? "ChatGPT (chatgpt.com/plugins \u2192 Add custom MCP server)" : i.platforms?.includes("chatgpt") ? "Claude (Settings \u2192 Connectors \u2192 Add custom connector) or ChatGPT (chatgpt.com/plugins \u2192 Add custom MCP server)" : "Claude (Settings \u2192 Connectors \u2192 Add custom connector)";
7673
7885
  lines.push(`Add ${i.publishedUrl} as a custom connector in ${where}.`);
7674
- lines.push(i.open ? "No sign-in: the data is public and every tool is read-only. Connect and ask." : `Sign in with the reviewer account in this packet${i.reviewerUsername ? ` (username ${i.reviewerUsername}; the password is in the packet, never in this text)` : ""}.`);
7886
+ lines.push(i.open ? "No sign-in: the data is public and every tool is read-only. Connect and ask." : `Sign in with the username and password entered in the credential fields of this form${i.reviewerUsername ? ` (username ${i.reviewerUsername})` : ""}: the sign-in page shows a username & password form under the provider buttons; no email, SMS or MFA step.`);
7675
7887
  const seen = /* @__PURE__ */ new Set();
7676
7888
  for (const p of i.prompts) {
7677
7889
  const called = toolsCalledBy(i, p);
7678
7890
  if (!called.length) continue;
7679
7891
  const row = i.rows.find((r) => r.tool === called[0].name && r.valid === "ok");
7680
7892
  called.forEach((t) => seen.add(t.name));
7681
- lines.push(`Ask: "${p}" \u2014 the assistant calls ${called.map(titleOf).join(" and ")}${row ? `; expect ${shapeOf(row)}` : ""}.`);
7893
+ lines.push(`Ask: "${p}" \u2014 uses ${called.map(titleOf).join(" and ")}${row ? `; expect ${humanizeShape(shapeOf(row))}` : ""}.`);
7682
7894
  }
7683
7895
  for (const r of i.rows) {
7684
7896
  if (seen.has(r.tool) || r.valid !== "ok") continue;
7685
7897
  const t = i.tools.find((x) => x.name === r.tool) ?? { name: r.tool };
7686
- lines.push(`Ask it to ${titleOf(t).replace(/^./, (c) => c.toLowerCase())} (${argsText(r.args)}); expect ${shapeOf(r)}.`);
7898
+ lines.push(`Ask it to ${titleOf(t).replace(/^./, (c) => c.toLowerCase())} (${argsText(r.args)}); expect ${humanizeShape(shapeOf(r))}.`);
7687
7899
  }
7688
7900
  const errs = i.rows.filter((r) => r.invalid === "error" && r.invalidText);
7689
7901
  for (const r of errs.slice(0, 2)) {
@@ -7708,7 +7920,7 @@ function draftDescription(i) {
7708
7920
  const working = i.prompts.filter((p) => (i.promptTools[p] ?? []).length);
7709
7921
  if (working.length) parts.push(`Ask it things like ${working.map((p) => `"${p}"`).join(", ")}.`);
7710
7922
  const ok = i.rows.find((r) => r.valid === "ok");
7711
- if (ok) parts.push(`What comes back is ${shapeOf(ok)}, which the assistant reads and explains.`);
7923
+ if (ok) parts.push(`Answers come back as ${humanizeShape(shapeOf(ok))}, explained in plain words.`);
7712
7924
  parts.push(readOnly(i) ? "Every tool only reads; nothing is created or changed." : "Writes change data in the signed-in account only.");
7713
7925
  return parts.join(" ");
7714
7926
  }
@@ -7774,6 +7986,7 @@ __export(connector_exercise_exports, {
7774
7986
  clientsFor: () => clientsFor,
7775
7987
  createPromptTester: () => createPromptTester,
7776
7988
  detectCodex: () => detectCodex,
7989
+ looksEmpty: () => looksEmpty,
7777
7990
  reportLink: () => reportLink,
7778
7991
  runConnectorAnswers: () => runConnectorAnswers,
7779
7992
  runConnectorExercise: () => runConnectorExercise
@@ -7801,6 +8014,16 @@ async function confirm(message, interactive2) {
7801
8014
  const a = await inquirer3.prompt([{ type: "confirm", name: "go", message, default: false }]);
7802
8015
  return a.go;
7803
8016
  }
8017
+ function looksEmpty(json) {
8018
+ if (json == null || json === "") return true;
8019
+ if (Array.isArray(json)) return json.length === 0;
8020
+ if (typeof json === "object") {
8021
+ const vals = Object.values(json);
8022
+ if (!vals.length) return true;
8023
+ return vals.every((v) => Array.isArray(v) && v.length === 0 || v === 0 || v === null || v === false || typeof v === "object" && v !== null && !Array.isArray(v) && Object.keys(v).length === 0);
8024
+ }
8025
+ return false;
8026
+ }
7804
8027
  async function runConnectorExercise(project, opts = {}) {
7805
8028
  const caller = await requireCaller();
7806
8029
  const { teamId } = await resolveActingTeam(caller, opts.team);
@@ -7833,17 +8056,21 @@ async function runConnectorExercise(project, opts = {}) {
7833
8056
  let configDir = null;
7834
8057
  let reviewerUsername = null;
7835
8058
  let userinfo = null;
8059
+ const seeded = {};
7836
8060
  try {
7837
8061
  if (!open) {
7838
8062
  const packet = await producer(caller, { method: "GET", path: `${projectPath(proj2)}/directory-packet?probe=0&force=1`, summary: "Reviewer account (for the login)" });
7839
8063
  const acct = packet.json?.test_account ?? null;
7840
8064
  if (!acct) throw new Error(`No reviewer account on ${proj2.projectName}: create it first (\`${B.cli} connector reviewer ${proj2.projectId}\`), or declare open mode for public read-only data.`);
7841
8065
  const login = await headlessPasswordLogin({ mcpUrl: publishedUrl, username: acct.username, password: acct.password }, deps);
7842
- if (!login.accessToken) throw new Error(`The reviewer could not sign in at ${login.failedAt}: ${login.detail}. Fix the sign-in and run again.`);
8066
+ if (!login.accessToken) {
8067
+ await producer(caller, { method: "PATCH", path: `${projectPath(proj2)}/directory-runs/${encodeURIComponent(runId2)}`, body: { notes: [{ kind: "failed", note: `reviewer sign-in failed at ${login.failedAt}: ${login.detail}` }], runner: "cli-direct", email: false }, summary: "Close the failed run" }).catch(() => null);
8068
+ throw new Error(`The reviewer could not sign in at ${login.failedAt}: ${login.detail}. Fix the sign-in and run again.`);
8069
+ }
7843
8070
  bearer = login.accessToken;
7844
8071
  asMeta = login.as;
7845
8072
  reviewerUsername = acct.username;
7846
- say(import_chalk26.default.dim(` signed in as the reviewer (${acct.username}); the token is revoked at the end`));
8073
+ say(import_chalk26.default.dim(` signed in as the reviewer (${acct.username}); the token expires in 1 h`));
7847
8074
  } else {
7848
8075
  say(import_chalk26.default.dim(" open mode: no sign-in; this run's calls count as yours"));
7849
8076
  }
@@ -7851,6 +8078,7 @@ async function runConnectorExercise(project, opts = {}) {
7851
8078
  const headers = { [RUN_HEADER]: runId2 };
7852
8079
  const call3 = (name, args) => mcpCall(publishedUrl, "tools/call", { name, arguments: args, _meta: { [RUN_META_KEY]: runId2 } }, { bearer, headers, userAgent: ua }, deps);
7853
8080
  const created = {};
8081
+ const emptyReads = [];
7854
8082
  const examples = { ...target2.metadata?.toolExamples ?? {} };
7855
8083
  const learned = {};
7856
8084
  const prompts = (target2.metadata?.examplePrompts ?? []).filter((p) => typeof p === "string");
@@ -7893,6 +8121,7 @@ async function runConnectorExercise(project, opts = {}) {
7893
8121
  row.validText = redactHead(v.text);
7894
8122
  row.args = args;
7895
8123
  if (v.outcome === "ok") row.resultShape = describeResult(v.text);
8124
+ if (role === "read" && v.outcome === "ok" && looksEmpty(parseResultJson(v.text))) emptyReads.push(t);
7896
8125
  if (v.outcome === "ok" && role === "create") {
7897
8126
  const id = extractCreatedId(parseResultJson(v.text));
7898
8127
  if (id) {
@@ -7915,6 +8144,46 @@ async function runConnectorExercise(project, opts = {}) {
7915
8144
  await producer(caller, { method: "PATCH", path: projectPath(proj2), body: { directory_metadata: { tool_examples: learned } }, summary: "Remember the working arguments" });
7916
8145
  say(import_chalk26.default.green(` \u2713 remembered the working arguments for ${Object.keys(learned).join(", ")} (used on every run from now on)`));
7917
8146
  }
8147
+ const creators = tools.filter((t) => toolRole(t) === "create");
8148
+ if (!open && !opts.promptsOnly && opts.seed !== false && emptyReads.length && creators.length) {
8149
+ const names = emptyReads.map((t) => t.name).join(", ");
8150
+ let go = opts.seed === true;
8151
+ if (!go && interactive2) go = await confirm(`${emptyReads.length} read${emptyReads.length > 1 ? "s" : ""} returned nothing for the reviewer (${names}). Create a few records the reviewer will see, through ${creators.map((c) => c.name).join(", ")}? They are kept, not deleted.`, interactive2);
8152
+ if (!go) {
8153
+ say(import_chalk26.default.dim(` seed skipped${interactive2 ? "" : " (non-interactive: pass --seed to create records for the reviewer, --no-seed to silence this)"}: ${names} stay empty for the reviewer (REVIEWER_ACCOUNT_POPULATED)`));
8154
+ notes.push({ kind: "seed", note: "skipped", tools: emptyReads.map((t) => t.name) });
8155
+ } else {
8156
+ for (const c of creators) {
8157
+ const args = validArgs(c, {}, examples[c.name]);
8158
+ const v = localOutcome(await call3(c.name, args));
8159
+ if (v.outcome !== "ok") {
8160
+ say(` ${mark(v.outcome)} seed via ${c.name} ${JSON.stringify(args)}: ${import_chalk26.default.dim(redactHead(v.text, 100) || v.outcome)}`);
8161
+ continue;
8162
+ }
8163
+ const id = extractCreatedId(parseResultJson(v.text));
8164
+ const idText = id ? String(id.value).slice(0, 64) : "(no id in the answer)";
8165
+ seeded[c.name] = [...seeded[c.name] ?? [], idText];
8166
+ notes.push({ kind: "seed", tool: c.name, note: `seeded ${id ? `${id.name}=${idText}` : "a record"}` });
8167
+ say(` ${import_chalk26.default.green("\u2713")} seeded via ${c.name}: ${idText} ${import_chalk26.default.dim("(kept for the reviewer)")}`);
8168
+ }
8169
+ if (Object.keys(seeded).length) {
8170
+ for (const t of emptyReads) {
8171
+ const row = rows.find((x) => x.tool === t.name);
8172
+ const args = validArgs(t, {}, examples[t.name]);
8173
+ const v = localOutcome(await call3(t.name, args));
8174
+ const nonEmpty = v.outcome === "ok" && !looksEmpty(parseResultJson(v.text));
8175
+ if (v.outcome === "ok") {
8176
+ row.valid = v.outcome;
8177
+ row.validText = redactHead(v.text);
8178
+ row.args = args;
8179
+ row.resultShape = describeResult(v.text);
8180
+ }
8181
+ say(` ${nonEmpty ? import_chalk26.default.green("\u2713") : import_chalk26.default.yellow("\u2013")} ${t.name} after seeding: ${import_chalk26.default.dim(nonEmpty ? v.text.replace(/\s+/g, " ").slice(0, 80) : "still nothing (a different filter or tenant? mark it --empty-ok if that is correct)")}`);
8182
+ }
8183
+ await producer(caller, { method: "PATCH", path: projectPath(proj2), body: { directory_metadata: { seeded } }, summary: "Remember what was seeded for the reviewer" }).catch((e) => say(import_chalk26.default.dim(` (could not store the seeded ids: ${e.message})`)));
8184
+ }
8185
+ }
8186
+ }
7918
8187
  const clients = clientsFor(opts.platforms);
7919
8188
  const claudeInstalled = deps.claudeInstalled ?? detectExternalClis().some((c) => c.kind === "claude");
7920
8189
  const codex = deps.codexInstalled !== void 0 ? { installed: deps.codexInstalled, loggedIn: deps.codexInstalled } : clients.includes("codex") ? detectCodex(deps.spawn ?? import_child_process5.spawnSync) : { installed: false, loggedIn: false };
@@ -8004,6 +8273,7 @@ async function runConnectorExercise(project, opts = {}) {
8004
8273
  reviewerUsername,
8005
8274
  tools,
8006
8275
  userinfo,
8276
+ seeded,
8007
8277
  notes: notes.filter((n) => (n.kind === "prompt" || n.kind === "negative") && typeof n.prompt === "string")
8008
8278
  };
8009
8279
  if (json) {
@@ -8565,141 +8835,6 @@ var init_connector_docs = __esm({
8565
8835
  }
8566
8836
  });
8567
8837
 
8568
- // src/lib/zip.ts
8569
- function crc32(buf) {
8570
- let c = 4294967295;
8571
- for (let i = 0; i < buf.length; i++) c = CRC_TABLE[(c ^ buf[i]) & 255] ^ c >>> 8;
8572
- return (c ^ 4294967295) >>> 0;
8573
- }
8574
- function zipStore(entries2) {
8575
- const parts = [];
8576
- const central = [];
8577
- let offset = 0;
8578
- const dosTime = 0, dosDate = 1 << 5 | 1;
8579
- for (const e of entries2) {
8580
- const name = new TextEncoder().encode(e.path.replace(/^\.\//, ""));
8581
- const crc = crc32(e.data);
8582
- const local = new Uint8Array(30 + name.length);
8583
- const lv = new DataView(local.buffer);
8584
- lv.setUint32(0, 67324752, true);
8585
- lv.setUint16(4, 20, true);
8586
- lv.setUint16(6, 2048, true);
8587
- lv.setUint16(8, 0, true);
8588
- lv.setUint16(10, dosTime, true);
8589
- lv.setUint16(12, dosDate, true);
8590
- lv.setUint32(14, crc, true);
8591
- lv.setUint32(18, e.data.length, true);
8592
- lv.setUint32(22, e.data.length, true);
8593
- lv.setUint16(26, name.length, true);
8594
- lv.setUint16(28, 0, true);
8595
- local.set(name, 30);
8596
- parts.push(local, e.data);
8597
- const cd = new Uint8Array(46 + name.length);
8598
- const cv = new DataView(cd.buffer);
8599
- cv.setUint32(0, 33639248, true);
8600
- cv.setUint16(4, 20, true);
8601
- cv.setUint16(6, 20, true);
8602
- cv.setUint16(8, 2048, true);
8603
- cv.setUint16(10, 0, true);
8604
- cv.setUint16(12, dosTime, true);
8605
- cv.setUint16(14, dosDate, true);
8606
- cv.setUint32(16, crc, true);
8607
- cv.setUint32(20, e.data.length, true);
8608
- cv.setUint32(24, e.data.length, true);
8609
- cv.setUint16(28, name.length, true);
8610
- cv.setUint16(30, 0, true);
8611
- cv.setUint16(32, 0, true);
8612
- cv.setUint16(34, 0, true);
8613
- cv.setUint16(36, 0, true);
8614
- cv.setUint32(38, 0, true);
8615
- cv.setUint32(42, offset, true);
8616
- cd.set(name, 46);
8617
- central.push(cd);
8618
- offset += local.length + e.data.length;
8619
- }
8620
- const cdSize = central.reduce((n, c) => n + c.length, 0);
8621
- const end = new Uint8Array(22);
8622
- const ev = new DataView(end.buffer);
8623
- ev.setUint32(0, 101010256, true);
8624
- ev.setUint16(4, 0, true);
8625
- ev.setUint16(6, 0, true);
8626
- ev.setUint16(8, entries2.length, true);
8627
- ev.setUint16(10, entries2.length, true);
8628
- ev.setUint32(12, cdSize, true);
8629
- ev.setUint32(16, offset, true);
8630
- ev.setUint16(20, 0, true);
8631
- const total = offset + cdSize + end.length;
8632
- const out = new Uint8Array(total);
8633
- let p = 0;
8634
- for (const part of [...parts, ...central, end]) {
8635
- out.set(part, p);
8636
- p += part.length;
8637
- }
8638
- return out;
8639
- }
8640
- function imageInfo(buf) {
8641
- const dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
8642
- if (buf.length >= 24 && buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) return { format: "png", width: dv.getUint32(16), height: dv.getUint32(20) };
8643
- if (buf.length >= 4 && buf[0] === 255 && buf[1] === 216) {
8644
- let i = 2;
8645
- while (i + 9 < buf.length) {
8646
- if (buf[i] !== 255) {
8647
- i++;
8648
- continue;
8649
- }
8650
- const marker2 = buf[i + 1];
8651
- if (marker2 === 216 || marker2 === 1 || marker2 >= 208 && marker2 <= 215) {
8652
- i += 2;
8653
- continue;
8654
- }
8655
- const len = dv.getUint16(i + 2);
8656
- if (marker2 >= 192 && marker2 <= 207 && marker2 !== 196 && marker2 !== 200 && marker2 !== 204) return { format: "jpeg", height: dv.getUint16(i + 5), width: dv.getUint16(i + 7) };
8657
- i += 2 + len;
8658
- }
8659
- return { format: "jpeg", width: null, height: null };
8660
- }
8661
- if (buf.length >= 30 && buf[0] === 82 && buf[1] === 73 && buf[2] === 70 && buf[3] === 70 && buf[8] === 87 && buf[9] === 69 && buf[10] === 66 && buf[11] === 80) {
8662
- const chunk = String.fromCharCode(buf[12], buf[13], buf[14], buf[15]);
8663
- if (chunk === "VP8X") return { format: "webp", width: 1 + (buf[24] | buf[25] << 8 | buf[26] << 16), height: 1 + (buf[27] | buf[28] << 8 | buf[29] << 16) };
8664
- if (chunk === "VP8 ") return { format: "webp", width: dv.getUint16(26, true) & 16383, height: dv.getUint16(28, true) & 16383 };
8665
- if (chunk === "VP8L") {
8666
- const b = dv.getUint32(21, true);
8667
- return { format: "webp", width: (b & 16383) + 1, height: (b >> 14 & 16383) + 1 };
8668
- }
8669
- return { format: "webp", width: null, height: null };
8670
- }
8671
- const head = new TextDecoder().decode(buf.subarray(0, 512)).trim();
8672
- if (/^(<\?xml[^>]*>\s*)?(<!--[\s\S]*?-->\s*)*<svg[\s>]/i.test(head)) return { format: "svg", width: null, height: null };
8673
- return { format: "unknown", width: null, height: null };
8674
- }
8675
- function logoProblem(buf) {
8676
- if (buf.length > 5 * 1024 * 1024) return `the icon is ${(buf.length / 1048576).toFixed(1)} MiB; OpenAI allows 5`;
8677
- const info = imageInfo(buf);
8678
- if (info.format === "unknown") return "the icon is not a PNG, JPEG, WebP or SVG";
8679
- if (info.width != null && info.height != null) {
8680
- if (info.width !== info.height) return `the icon is ${info.width}\xD7${info.height}; OpenAI wants a square`;
8681
- if (info.width < 48) return `the icon is ${info.width} px; OpenAI wants at least 48`;
8682
- if (info.width > 4096) return `the icon is ${info.width} px; OpenAI allows at most 4096`;
8683
- }
8684
- return null;
8685
- }
8686
- var CRC_TABLE, LOGO_EXT;
8687
- var init_zip = __esm({
8688
- "src/lib/zip.ts"() {
8689
- "use strict";
8690
- CRC_TABLE = (() => {
8691
- const t = new Uint32Array(256);
8692
- for (let n = 0; n < 256; n++) {
8693
- let c = n;
8694
- for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
8695
- t[n] = c >>> 0;
8696
- }
8697
- return t;
8698
- })();
8699
- LOGO_EXT = { png: "png", jpeg: "jpg", webp: "webp", svg: "svg", unknown: "bin" };
8700
- }
8701
- });
8702
-
8703
8838
  // src/lib/plugin-package.ts
8704
8839
  var plugin_package_exports = {};
8705
8840
  __export(plugin_package_exports, {
@@ -8996,7 +9131,7 @@ function requireTool(cat, tool) {
8996
9131
  function withOverride(cat, tool, flags) {
8997
9132
  requireTool(cat, tool);
8998
9133
  const patch = {};
8999
- for (const k of ["readOnly", "destructive", "idempotent", "confirm", "alwaysAsync"]) {
9134
+ for (const k of ["readOnly", "destructive", "idempotent", "confirm", "alwaysAsync", "openWorld"]) {
9000
9135
  if (typeof flags[k] === "boolean") patch[k] = flags[k];
9001
9136
  }
9002
9137
  if (typeof flags.title === "string") {
@@ -9005,7 +9140,7 @@ function withOverride(cat, tool, flags) {
9005
9140
  patch.title = t;
9006
9141
  }
9007
9142
  if (Object.keys(patch).length === 0) {
9008
- throw new Error("Nothing to set. Pass --title, --read-only, --destructive, --idempotent, --confirm, --always-async (or their --no-\u2026 forms).");
9143
+ throw new Error("Nothing to set. Pass --title, --read-only, --destructive, --idempotent, --confirm, --always-async, --open-world (or their --no-\u2026 forms).");
9009
9144
  }
9010
9145
  return { ...cat, overrides: { ...cat.overrides ?? {}, [tool]: { ...cat.overrides?.[tool] ?? {}, ...patch } } };
9011
9146
  }
@@ -12553,6 +12688,9 @@ var PROMPT_FOR_RULE = {
12553
12688
  DESCRIPTION_LEN_CLAUDE: { key: "description", message: "Description", limit: 2e3, portal: "Claude" },
12554
12689
  DESCRIPTION_LEN_CHATGPT: { key: "description", message: "Description", limit: 4e3, portal: "ChatGPT" },
12555
12690
  PROMPT_LEN_CHATGPT: { key: "prompt", message: "Example prompt", multi: true, limit: 128, portal: "ChatGPT" },
12691
+ // WP5: Anthropic's slug (pre-filled from the listing name; permanent once published) and the policy's high-risk question.
12692
+ SLUG_SET: { key: "slug", message: "Listing slug (lowercase-with-hyphens; permanent once published, claude.ai/directory/connectors/<slug>):" },
12693
+ HIGH_RISK_DECLARED: { key: "highRisk", message: "High-risk use cases \u2014 does it serve legal, healthcare, insurance or finance decisions? (none, or a comma-separated list):" },
12556
12694
  // ChatGPT only: the portal issues this token (Plugins → Connect MCP server); we serve it verbatim.
12557
12695
  OPENAI_DOMAIN_CHALLENGE: { key: "openaiChallenge", message: "OpenAI domain challenge token, from platform.openai.com/plugins \u2192 Connect MCP server (Enter to skip for now):" }
12558
12696
  };
@@ -13004,6 +13142,10 @@ async function runConnector(target2, opts) {
13004
13142
  const changed = await fixToolDescriptions(proj2, r.report.project.environment, interactive2, say);
13005
13143
  if (changed) r = await fetchDirectoryReport(caller, proj2, { platforms });
13006
13144
  }
13145
+ if (r.report.results.some((x) => x.id === "TOOL_OPENWORLD_CONFIRMED" && !x.pass)) {
13146
+ const changed = await confirmOpenWorld(proj2, r.report.project.environment, interactive2, opts.openWorld, isOpenMode(r), say);
13147
+ if (changed) r = await fetchDirectoryReport(caller, proj2, { platforms });
13148
+ }
13007
13149
  if (await askOpenMode(caller, proj2, r, opts, interactive2, say)) r = await fetchDirectoryReport(caller, proj2, { platforms });
13008
13150
  let reviewer = null;
13009
13151
  if (r.report.results.some((x) => x.id === "TEST_ACCOUNT_SET" && !x.pass)) {
@@ -13035,6 +13177,7 @@ async function runConnector(target2, opts) {
13035
13177
  const t = tester;
13036
13178
  say(import_chalk30.default.dim(` trying it through ${t.clients.map((c) => c === "claude" ? "Claude Code" : "Codex CLI").join(" and ")}\u2026`));
13037
13179
  const res = await t.test(p);
13180
+ if (res.tools.length) await patchProject(caller, proj2, { directory_metadata: { prompt_tools: { [p.trim()]: res.tools } } }, "Remember which tool the prompt called").catch(() => null);
13038
13181
  for (const [client, r2] of Object.entries(res.byClient)) say(r2.called ? import_chalk30.default.green(` \u2713 ${client === "claude" ? "Claude" : "Codex"} called ${r2.tools.join(", ")}`) : import_chalk30.default.yellow(` \u2013 ${client === "claude" ? "Claude" : "Codex"} called no tool`));
13039
13182
  return res;
13040
13183
  };
@@ -13068,7 +13211,7 @@ async function runConnector(target2, opts) {
13068
13211
  }
13069
13212
  if (run2) {
13070
13213
  const { runConnectorExercise: runConnectorExercise2 } = await Promise.resolve().then(() => (init_connector_exercise(), connector_exercise_exports));
13071
- const ran = await runConnectorExercise2(proj2.projectId, { team: opts.team ?? proj2.teamId, apiversion: opts.apiversion, json, yes: opts.yes, noEmail: opts.email === false, report: false, platforms: [...platforms], provenPromptNotes });
13214
+ const ran = await runConnectorExercise2(proj2.projectId, { team: opts.team ?? proj2.teamId, apiversion: opts.apiversion, json, yes: opts.yes, noEmail: opts.email === false, report: false, platforms: [...platforms], provenPromptNotes, seed: opts.seed });
13072
13215
  if (interactive2 && ran.failedPrompts.length) {
13073
13216
  for (const [p, clients] of Object.entries(ran.failedByClient ?? {})) say(import_chalk30.default.yellow(` \u2013 "${p.slice(0, 60)}": no tool call in ${clients.map((c) => c === "claude" ? "Claude Code" : "Codex CLI").join(" and ")}`));
13074
13217
  const { rewritten, prompts } = await rewriteFailedPrompts(r, ran.failedPrompts, saveAnswer, say);
@@ -13087,6 +13230,7 @@ async function runConnector(target2, opts) {
13087
13230
  r = await fetchDirectoryReport(caller, proj2, { platforms, publishedUrl: r.report.project.publishedUrl ?? null, extraProbes, listing: listingOf(r) });
13088
13231
  }
13089
13232
  }
13233
+ if (opts.waitLogin !== false && interactive2) r = await waitForClaudeLogin(caller, proj2, r, platforms, interactive2, say);
13090
13234
  if (!readyExceptSnapshot(r, platforms)) {
13091
13235
  if (!domain2.active && domain2.hostname && !json) {
13092
13236
  console.error(import_chalk30.default.yellow(`
@@ -13095,6 +13239,13 @@ async function runConnector(target2, opts) {
13095
13239
  exitWithReport(r, proj2, platforms, json, interactive2);
13096
13240
  }
13097
13241
  r = await fetchDirectoryReport(caller, proj2, { platforms, refreshSnapshot: true, publishedUrl: r.report.project.publishedUrl ?? null, listing: listingOf(r) });
13242
+ let watching = false;
13243
+ if (opts.watch !== false) {
13244
+ watching = await producer(caller, { method: "POST", path: `${projectPath3(proj2)}/directory-watch`, body: { enabled: true }, summary: "Turn on the nightly re-check" }).then(() => true).catch((e) => {
13245
+ say(import_chalk30.default.dim(` (nightly re-check not switched on: ${e.message})`));
13246
+ return false;
13247
+ });
13248
+ }
13098
13249
  const probes = await packetProbes(r);
13099
13250
  const packet = await producer(caller, { method: "POST", path: `${projectPath3(proj2)}/directory-packet`, body: { platforms, ...probes ? { probes } : {} }, summary: "Submission packet" });
13100
13251
  const dir = opts.out ?? process.cwd();
@@ -13129,9 +13280,42 @@ async function runConnector(target2, opts) {
13129
13280
  console.log(` Submission packet: ${import_chalk30.default.cyan(mdPath)} and ${import_chalk30.default.cyan(jsonPath)}${zipPath ? `
13130
13281
  OpenAI plugin package: ${import_chalk30.default.cyan(zipPath)}` : ""}`);
13131
13282
  console.log(import_chalk30.default.dim(isOpenMode(r) ? " (open mode: no reviewer credentials in the packet; the connector takes no sign-in)" : " (the packet carries the reviewer credentials \u2014 keep it out of git)"));
13132
- printSubmitSteps(r, mdPath, platforms);
13283
+ printSubmitSteps(r, mdPath, platforms, watching);
13133
13284
  console.log(import_chalk30.default.dim(` ${r.report.disclaimer}`));
13134
13285
  }
13286
+ async function waitForClaudeLogin(caller, proj2, r, platforms, interactive2, say, deps = {}) {
13287
+ const rule = r.report.results.find((x) => x.id === "OAUTH_FLOW_COMPLETED");
13288
+ if (!rule || rule.pass || !interactive2 || isOpenMode(r)) return r;
13289
+ const url = r.report.project.publishedUrl ?? "";
13290
+ const name = r.target?.metadata?.serverName ?? proj2.projectId;
13291
+ const alias = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || proj2.projectId;
13292
+ say("");
13293
+ say(import_chalk30.default.bold(" One sign-in from a real Claude client is the last proof (OAUTH_FLOW_COMPLETED). In another terminal:"));
13294
+ say(` claude mcp add --transport http ${alias} ${url}`);
13295
+ say(` claude then /mcp \u2192 ${alias} \u2192 sign in with the reviewer account`);
13296
+ say(import_chalk30.default.dim(` or in claude.ai: Settings \u2192 Connectors \u2192 Add custom connector \u2192 ${url} (what a reviewer does).`));
13297
+ say(import_chalk30.default.dim(` Afterwards: claude mcp remove ${alias}`));
13298
+ const { default: inquirer3 } = await import("inquirer");
13299
+ const { go } = await inquirer3.prompt([{ type: "confirm", name: "go", message: "Sign in from Claude now, then press Enter to check (n skips; the verdict stays red on this rule):", default: true }]);
13300
+ if (!go) return r;
13301
+ const sleep2 = deps.sleep ?? ((ms2) => new Promise((res) => setTimeout(res, ms2)));
13302
+ const maxMs = deps.maxMs ?? 10 * 6e4;
13303
+ const started = Date.now();
13304
+ let cur = r;
13305
+ for (; ; ) {
13306
+ cur = await fetchDirectoryReport(caller, proj2, { platforms });
13307
+ const now = cur.report.results.find((x) => x.id === "OAUTH_FLOW_COMPLETED");
13308
+ if (now?.pass) {
13309
+ say(import_chalk30.default.green(` \u2713 ${now.message ?? "a Claude client completed the sign-in"}`));
13310
+ return cur;
13311
+ }
13312
+ if (Date.now() - started >= maxMs) {
13313
+ say(import_chalk30.default.yellow(" \u2013 no Claude sign-in seen in 10 minutes; run again after signing in."));
13314
+ return cur;
13315
+ }
13316
+ await sleep2(2e4);
13317
+ }
13318
+ }
13135
13319
  async function rewriteFailedPrompts(r, failed, save, say) {
13136
13320
  const current = currentListingValue(r, "prompt");
13137
13321
  const list = Array.isArray(current) ? [...current] : [];
@@ -13234,7 +13418,7 @@ var withProject = (s, projectId) => s.replace(/<project>/g, projectId);
13234
13418
  var ANSWER_RULES = ["PORTAL_ANSWERS_COMPLETE", "HEALTH_SPONSORED_DECLARED", "API_OWNERSHIP_DOMAIN", "WEBSITE_URL_HTTPS_200", "SUPPORT_URL_HTTPS_200", "TERMS_URL_HTTPS_200", "DEVELOPER_NAME_SET", "CATEGORY_SET", "DEMO_RECORDING_URL_SET", "RELEASE_NOTES_SET", "SHORT_DESCRIPTION_SET", "CHATGPT_TEST_CASES_COMPLETE"];
13235
13419
  function exitWithReport(r, proj2, platforms, json, interactive2 = false) {
13236
13420
  if (json) {
13237
- process.stdout.write(JSON.stringify({ ready: false, project: proj2.projectId, headline: r.rendered.headline, report: r.report }) + "\n");
13421
+ writeJsonLine({ ready: false, project: proj2.projectId, headline: r.rendered.headline, report: r.report });
13238
13422
  process.exit(1);
13239
13423
  }
13240
13424
  printReport(r, false);
@@ -13245,6 +13429,10 @@ function exitWithReport(r, proj2, platforms, json, interactive2 = false) {
13245
13429
  console.error(import_chalk30.default.red(`
13246
13430
  Not ready: ${blocking.length} blocker${blocking.length === 1 ? "" : "s"} (${blocking.map((x) => x.id).join(", ")}).`));
13247
13431
  const name = proj2.projectId;
13432
+ if (!interactive2) {
13433
+ const flags = flagsForRules(real.map((x) => x.id), platforms);
13434
+ if (flags.length) console.error(` Non-interactive: pass ${flags.join(" ")} to \`${B.cli} connector ${name} --yes\` (or run it in a terminal to be asked).`);
13435
+ }
13248
13436
  const answerish = real.filter((x) => ANSWER_RULES.includes(x.id));
13249
13437
  if (answerish.length && answerish.every((x) => !x.platforms.includes("claude"))) console.error(` Next: ${withProject(answerish[0].fix ?? answerish[0].message ?? answerish[0].title, name)}`);
13250
13438
  else if (answerish.length) console.error(` Next: run \`${B.cli} connector ${name}${platforms.includes("chatgpt") ? ` --compliant ${platforms.length > 1 ? "all" : "chatgpt"}` : ""}\` in a terminal with the exercise on \u2014 it asks what only you can answer and writes the use cases, the reviewer instructions and the description from the run; or pass them: \`${B.cli} connector answers ${name} --use-cases \u2026 --data-handling own --health-data no --sponsored-content no\`.`);
@@ -13252,6 +13440,66 @@ Not ready: ${blocking.length} blocker${blocking.length === 1 ? "" : "s"} (${bloc
13252
13440
  console.error(import_chalk30.default.dim(` Report page: ${DASHBOARD_BASE6}/directory/${encodeURIComponent(proj2.projectId)}/${encodeURIComponent(proj2.apiVersion)}`));
13253
13441
  process.exit(1);
13254
13442
  }
13443
+ function flagsForRules(ids, platforms) {
13444
+ const out = [];
13445
+ const kebab = (k) => `--${k.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase()}`;
13446
+ const seen = /* @__PURE__ */ new Set();
13447
+ const add = (f2) => {
13448
+ if (!seen.has(f2)) {
13449
+ seen.add(f2);
13450
+ out.push(f2);
13451
+ }
13452
+ };
13453
+ for (const id of ids) {
13454
+ const q = PROMPT_FOR_RULE[id];
13455
+ if (q) {
13456
+ add(`${kebab(q.key)} <\u2026>`);
13457
+ continue;
13458
+ }
13459
+ if (id.startsWith("ATTEST_")) {
13460
+ const a = ATTESTATIONS.find((x) => `ATTEST_${x.id.toUpperCase()}` === id || id === `ATTEST_${x.flags[0].replace(/-/g, "_").toUpperCase()}`);
13461
+ add(a ? `--attest ${a.flags[0]}` : "--attest <\u2026>");
13462
+ continue;
13463
+ }
13464
+ switch (id) {
13465
+ case "HEALTH_SPONSORED_DECLARED":
13466
+ add("--health-data yes|no");
13467
+ add("--sponsored-content yes|no");
13468
+ break;
13469
+ case "HIGH_RISK_DECLARED":
13470
+ add("--high-risk none|legal,\u2026");
13471
+ break;
13472
+ case "TOOL_OPENWORLD_CONFIRMED":
13473
+ add("--open-world yes|no");
13474
+ break;
13475
+ case "REVIEWER_ACCOUNT_POPULATED":
13476
+ add("--seed");
13477
+ break;
13478
+ case "API_OWNERSHIP_DOMAIN":
13479
+ add('--data-handling own|proxied "<permission>"');
13480
+ break;
13481
+ case "DOCS_URL":
13482
+ case "DOCS_PAGE_LIVE":
13483
+ case "DOCS_IS_PROSE":
13484
+ add("--docs-url <https://\u2026>");
13485
+ break;
13486
+ case "PORTAL_ANSWERS_COMPLETE":
13487
+ add(`(then \`connector answers\` with --use-cases --prerequisites --access --company-name --company-website --company-contact)`);
13488
+ break;
13489
+ case "CHATGPT_TEST_CASES_COMPLETE":
13490
+ case "NEGATIVE_PROMPTS_HOLD":
13491
+ add("--negative-prompt <\u2026> (three)");
13492
+ break;
13493
+ case "OPENAI_DOMAIN_CHALLENGE":
13494
+ add("--openai-challenge <token>");
13495
+ break;
13496
+ default:
13497
+ break;
13498
+ }
13499
+ }
13500
+ void platforms;
13501
+ return out;
13502
+ }
13255
13503
  var TOOL_DESC_RULE = "TOOL_DESC_NONEMPTY_SPECIFIC";
13256
13504
  var UTILITY_PATH = /(health|ping|status|openapi|swagger|docs|spec|whoami|version)/i;
13257
13505
  var SPECIFIC_MIN = 12;
@@ -13264,7 +13512,10 @@ function needsDescription(t) {
13264
13512
  if (!d || d.length < SPECIFIC_MIN) return true;
13265
13513
  const m = (t["x-route"]?.method ?? "get").toUpperCase();
13266
13514
  const p = t["x-route"]?.path ?? "";
13267
- return new RegExp(`^(${m}\\s+${p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}|${t.name})\\.?$`, "i").test(d);
13515
+ if (new RegExp(`^(${m}\\s+${p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}|${t.name})\\.?$`, "i").test(d)) return true;
13516
+ const norm = (x) => x.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
13517
+ const title = (t.summary ?? "").trim() || t.name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ");
13518
+ return norm(d) === norm(title) || d.length < 40;
13268
13519
  }
13269
13520
  function derivedDescription(t) {
13270
13521
  const m = (t["x-route"]?.method ?? "get").toUpperCase();
@@ -13274,9 +13525,40 @@ function derivedDescription(t) {
13274
13525
  const plural = segments.filter((s) => !s.startsWith("{")).pop()?.replace(/[-_]/g, " ") ?? "result";
13275
13526
  const one = segments[segments.length - 1]?.startsWith("{");
13276
13527
  const noun = one ? plural.replace(/s$/, "") : plural;
13277
- const returns = m === "GET" ? `Returns the ${noun} as JSON.` : m === "POST" ? `Creates it and returns the new ${noun.replace(/s$/, "")} as JSON.` : m === "DELETE" ? `Removes it and returns the result.` : `Applies the change and returns the updated ${noun.replace(/s$/, "")} as JSON.`;
13528
+ const props = t.input_schema?.properties ?? {};
13529
+ const required = t.input_schema?.required ?? [];
13530
+ const firstParam = required.map((k) => [k, props[k]]).find(([, v]) => v && typeof v.description === "string" && v.description.trim()) ?? Object.entries(props).find(([, v]) => v && typeof v.description === "string" && v.description.trim());
13531
+ const takes = firstParam ? ` Takes ${firstParam[0]}: ${firstParam[1].description.trim().replace(/\.$/, "")}.` : "";
13532
+ const returns = m === "GET" ? `Returns the ${noun} as JSON.${takes}` : m === "POST" ? `Creates it and returns the new ${noun.replace(/s$/, "")} as JSON.` : m === "DELETE" ? `Removes it and returns the result.` : `Applies the change and returns the updated ${noun.replace(/s$/, "")} as JSON.`;
13278
13533
  return `${summary.replace(/\.$/, "")}. ${m} ${p}. ${returns}`;
13279
13534
  }
13535
+ async function confirmOpenWorld(proj2, environment, interactive2, flag, openMode, say) {
13536
+ const { loadCatalogue: loadCatalogue2, saveCatalogue: saveCatalogue2 } = await Promise.resolve().then(() => (init_mcp(), mcp_exports));
13537
+ const t = { projectId: proj2.projectId, apiVersion: proj2.apiVersion, environment: environment || "dev" };
13538
+ let cat;
13539
+ try {
13540
+ cat = await loadCatalogue2(t);
13541
+ } catch {
13542
+ return false;
13543
+ }
13544
+ const unset = cat.tools.map((x) => x.name).filter((n) => typeof cat.overrides?.[n]?.openWorld !== "boolean");
13545
+ if (!unset.length) return false;
13546
+ let answer = flag ? /^(y|yes|true|1)$/i.test(flag.trim()) : null;
13547
+ if (answer === null && !interactive2) {
13548
+ say(import_chalk30.default.dim(` openWorldHint not answered for ${unset.length} tool${unset.length === 1 ? "" : "s"} (pass --open-world yes|no)`));
13549
+ return false;
13550
+ }
13551
+ if (answer === null) {
13552
+ const { default: inquirer3 } = await import("inquirer");
13553
+ const { ow } = await inquirer3.prompt([{ type: "confirm", name: "ow", message: `Do the tools reach public or open-ended data \u2014 web pages, other people's records \u2014 (y), or only the signed-in account's own data (n)? Applies to ${unset.length === cat.tools.length ? "every tool" : `${unset.length} tools`}; change one later with \`${B.cli} mcp ops ${proj2.projectId} set <tool> --open-world|--no-open-world\`.`, default: openMode }]);
13554
+ answer = ow;
13555
+ }
13556
+ cat.overrides = { ...cat.overrides ?? {} };
13557
+ for (const n of unset) cat.overrides[n] = { ...cat.overrides[n] ?? {}, openWorld: answer };
13558
+ await saveCatalogue2(t, cat);
13559
+ say(import_chalk30.default.green(` \u2713 openWorldHint ${answer ? "true" : "false"} on ${unset.length === cat.tools.length ? "every tool" : unset.join(", ")}`));
13560
+ return true;
13561
+ }
13280
13562
  async function fixToolDescriptions(proj2, environment, interactive2, say) {
13281
13563
  const { loadCatalogue: loadCatalogue2, saveCatalogue: saveCatalogue2 } = await Promise.resolve().then(() => (init_mcp(), mcp_exports));
13282
13564
  const t = { projectId: proj2.projectId, apiVersion: proj2.apiVersion, environment: environment || "dev" };
@@ -13364,7 +13646,7 @@ async function headStatus(u) {
13364
13646
  return 0;
13365
13647
  }
13366
13648
  }
13367
- function printSubmitSteps(r, mdPath, platforms = PLATFORMS) {
13649
+ function printSubmitSteps(r, mdPath, platforms = PLATFORMS, watching = false) {
13368
13650
  const url = r.report.project.publishedUrl ?? "(the MCP URL in the packet)";
13369
13651
  const open = isOpenMode(r);
13370
13652
  const claude = platforms.includes("claude");
@@ -13374,7 +13656,7 @@ function printSubmitSteps(r, mdPath, platforms = PLATFORMS) {
13374
13656
  `Test it yourself first: add ${import_chalk30.default.cyan(url)} as a custom connector in ${where}, ${open ? "no sign-in needed (open mode)" : "sign in with the reviewer account"}, run a tool.`,
13375
13657
  ...claude ? [`Claude: ${import_chalk30.default.underline(PORTALS.claude)} \u2192 Submit new \u2192 MCP connector. Paste the listing, ${open ? "Authentication: None with the access instructions" : "the reviewer credentials"} and the test steps from ${import_chalk30.default.cyan(import_path7.default.basename(mdPath))}.`] : [],
13376
13658
  ...chatgpt ? [`ChatGPT: ${import_chalk30.default.underline(PORTALS.chatgpt)} \u2192 upload ${import_chalk30.default.cyan(`connector-plugin-${r.report.project.id}.zip`)} \u2192 MCPs \u2192 Connect \u2192 Verify Domain \u2192 authorize \u2192 Scan Tools; paste the reviewer credentials under Review details; add the demo recording URL and the release notes; confirm the attestations. The MCP hostname is permanent for the OpenAI listing.`] : [],
13377
- open ? "A nightly re-check emails you if the listing drifts." : "Keep the reviewer account alive; a nightly re-check emails you if the listing drifts."
13659
+ watching ? open ? "A nightly re-check is on: it emails the team owner if the listing drifts or a new check is added." : "Keep the reviewer account alive. A nightly re-check is on: it emails the team owner if the listing drifts or a new check is added." : open ? "The nightly re-check is off (--no-watch); turn it on in the dashboard to be emailed when the listing drifts." : "Keep the reviewer account alive. The nightly re-check is off (--no-watch); turn it on in the dashboard to be emailed when the listing drifts."
13378
13660
  ];
13379
13661
  console.log();
13380
13662
  console.log(import_chalk30.default.bold(" Submit (you do this part; the packet has every value to paste):"));
@@ -14415,7 +14697,7 @@ async function runLogout(opts = {}) {
14415
14697
 
14416
14698
  // src/commands/flush.ts
14417
14699
  var import_chalk37 = __toESM(require("chalk"));
14418
- var fs20 = __toESM(require("fs"));
14700
+ var fs21 = __toESM(require("fs"));
14419
14701
  var path17 = __toESM(require("path"));
14420
14702
  init_auth();
14421
14703
  init_external_mcp();
@@ -14424,7 +14706,7 @@ async function runFlush(opts) {
14424
14706
  const demos = await stopDemos2().catch(() => 0);
14425
14707
  if (demos) console.log(import_chalk37.default.dim(` Stopped ${demos} running demo(s).`));
14426
14708
  const dir = getApiblazeDir();
14427
- const entries2 = fs20.existsSync(dir) ? fs20.readdirSync(dir) : [];
14709
+ const entries2 = fs21.existsSync(dir) ? fs21.readdirSync(dir) : [];
14428
14710
  if (!entries2.length) {
14429
14711
  console.log(import_chalk37.default.dim("Nothing to flush \u2014 no local apiblaze config found."));
14430
14712
  return;
@@ -14437,7 +14719,7 @@ async function runFlush(opts) {
14437
14719
  const footprint = apiblazeMcpFootprint();
14438
14720
  const mdFiles = ["claude", "codex"].map((k) => ({ k, file: agentInstructionFile(k) })).filter(({ file: file2 }) => {
14439
14721
  try {
14440
- return fs20.readFileSync(file2, "utf-8").includes("<!-- apiblaze:mcp:");
14722
+ return fs21.readFileSync(file2, "utf-8").includes("<!-- apiblaze:mcp:");
14441
14723
  } catch {
14442
14724
  return false;
14443
14725
  }
@@ -14462,7 +14744,7 @@ async function runFlush(opts) {
14462
14744
  let removed = 0;
14463
14745
  for (const e of entries2) {
14464
14746
  try {
14465
- fs20.rmSync(path17.join(dir, e), { recursive: true, force: true });
14747
+ fs21.rmSync(path17.join(dir, e), { recursive: true, force: true });
14466
14748
  removed++;
14467
14749
  } catch (err) {
14468
14750
  console.log(import_chalk37.default.red(` \u2717 could not remove ${e}: ${err instanceof Error ? err.message : "error"}`));
@@ -14477,7 +14759,7 @@ async function runFlush(opts) {
14477
14759
  for (const { k, file: file2 } of mdFiles) {
14478
14760
  let text = "";
14479
14761
  try {
14480
- text = fs20.readFileSync(file2, "utf-8");
14762
+ text = fs21.readFileSync(file2, "utf-8");
14481
14763
  } catch {
14482
14764
  continue;
14483
14765
  }
@@ -18383,7 +18665,7 @@ async function providersMenu(cBase, clientLabel) {
18383
18665
  }
18384
18666
 
18385
18667
  // src/commands/spec.ts
18386
- var fs21 = __toESM(require("fs"));
18668
+ var fs22 = __toESM(require("fs"));
18387
18669
  var import_chalk62 = __toESM(require("chalk"));
18388
18670
  var import_ora27 = __toESM(require("ora"));
18389
18671
  init_admin();
@@ -18406,7 +18688,7 @@ async function runSpecSet(project, opts) {
18406
18688
  }
18407
18689
  let specContent;
18408
18690
  try {
18409
- specContent = fs21.readFileSync(opts.file, "utf-8");
18691
+ specContent = fs22.readFileSync(opts.file, "utf-8");
18410
18692
  } catch {
18411
18693
  console.error(import_chalk62.default.red(`Cannot read file: ${opts.file}`));
18412
18694
  process.exit(1);
@@ -20231,28 +20513,28 @@ async function runConsumerApikeys(opts) {
20231
20513
  // src/commands/sidecar.ts
20232
20514
  var import_chalk68 = __toESM(require("chalk"));
20233
20515
  var import_ora33 = __toESM(require("ora"));
20234
- var fs22 = __toESM(require("fs"));
20516
+ var fs23 = __toESM(require("fs"));
20235
20517
  var path18 = __toESM(require("path"));
20236
20518
  init_admin();
20237
20519
  init_resolve();
20238
20520
  init_auth();
20239
20521
  function detectNextProject(root) {
20240
- const hasConfig = ["next.config.js", "next.config.mjs", "next.config.ts"].some((f2) => fs22.existsSync(path18.join(root, f2)));
20522
+ const hasConfig = ["next.config.js", "next.config.mjs", "next.config.ts"].some((f2) => fs23.existsSync(path18.join(root, f2)));
20241
20523
  let hasDep = false;
20242
20524
  try {
20243
- const pkg = JSON.parse(fs22.readFileSync(path18.join(root, "package.json"), "utf8"));
20525
+ const pkg = JSON.parse(fs23.readFileSync(path18.join(root, "package.json"), "utf8"));
20244
20526
  hasDep = !!(pkg.dependencies?.next || pkg.devDependencies?.next);
20245
20527
  } catch {
20246
20528
  }
20247
- const appDir = fs22.existsSync(path18.join(root, "app")) || fs22.existsSync(path18.join(root, "src", "app"));
20248
- const pagesDir = fs22.existsSync(path18.join(root, "pages")) || fs22.existsSync(path18.join(root, "src", "pages"));
20529
+ const appDir = fs23.existsSync(path18.join(root, "app")) || fs23.existsSync(path18.join(root, "src", "app"));
20530
+ const pagesDir = fs23.existsSync(path18.join(root, "pages")) || fs23.existsSync(path18.join(root, "src", "pages"));
20249
20531
  return { found: hasConfig || hasDep || appDir || pagesDir, router: appDir ? "app" : pagesDir ? "pages" : null };
20250
20532
  }
20251
20533
  function upsertEnvLocal(root, token) {
20252
20534
  const p = path18.join(root, ".env.local");
20253
20535
  let existing = "";
20254
20536
  try {
20255
- existing = fs22.readFileSync(p, "utf8");
20537
+ existing = fs23.readFileSync(p, "utf8");
20256
20538
  } catch {
20257
20539
  }
20258
20540
  const had = /^APIBLAZE_API_KEY=/m.test(existing) || /^APIBLAZE_TOKEN=/m.test(existing);
@@ -20267,15 +20549,15 @@ function upsertEnvLocal(root, token) {
20267
20549
  next = (next.endsWith("\n") ? next : next + "\n") + `APIBLAZE_SIDECAR_VERBOSE=true
20268
20550
  `;
20269
20551
  }
20270
- fs22.writeFileSync(p, next);
20552
+ fs23.writeFileSync(p, next);
20271
20553
  return had ? "rotated" : "created";
20272
20554
  }
20273
20555
  function installSidecarPackage(root) {
20274
- if (fs22.existsSync(path18.join(root, "node_modules", "apiblaze", "package.json"))) {
20556
+ if (fs23.existsSync(path18.join(root, "node_modules", "apiblaze", "package.json"))) {
20275
20557
  console.log(` ${import_chalk68.default.green("\u2713")} apiblaze package already installed`);
20276
20558
  return;
20277
20559
  }
20278
- const has = (f2) => fs22.existsSync(path18.join(root, f2));
20560
+ const has = (f2) => fs23.existsSync(path18.join(root, f2));
20279
20561
  const pm = has("bun.lockb") || has("bun.lock") ? { cmd: "bun", add: "add" } : has("pnpm-lock.yaml") ? { cmd: "pnpm", add: "add" } : has("yarn.lock") ? { cmd: "yarn", add: "add" } : { cmd: "npm", add: "install" };
20280
20562
  const spinner = (0, import_ora33.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
20281
20563
  try {
@@ -20288,7 +20570,7 @@ function installSidecarPackage(root) {
20288
20570
  }
20289
20571
  function readEnvKey(root) {
20290
20572
  try {
20291
- const s = fs22.readFileSync(path18.join(root, ".env.local"), "utf8");
20573
+ const s = fs23.readFileSync(path18.join(root, ".env.local"), "utf8");
20292
20574
  const m = s.match(/^APIBLAZE_API_KEY=(.+)$/m) ?? s.match(/^APIBLAZE_TOKEN=(.+)$/m);
20293
20575
  return m ? m[1].trim() : null;
20294
20576
  } catch {
@@ -20299,13 +20581,13 @@ function ensureGitignored(root) {
20299
20581
  const p = path18.join(root, ".gitignore");
20300
20582
  let c = "";
20301
20583
  try {
20302
- c = fs22.readFileSync(p, "utf8");
20584
+ c = fs23.readFileSync(p, "utf8");
20303
20585
  } catch {
20304
20586
  }
20305
- if (!/^\.env\.local$/m.test(c) && !/^\.env\*/m.test(c)) fs22.writeFileSync(p, (c && !c.endsWith("\n") ? c + "\n" : c) + ".env.local\n");
20587
+ if (!/^\.env\.local$/m.test(c) && !/^\.env\*/m.test(c)) fs23.writeFileSync(p, (c && !c.endsWith("\n") ? c + "\n" : c) + ".env.local\n");
20306
20588
  }
20307
20589
  function wireInstrumentation(root) {
20308
- const existing = ["instrumentation.ts", "instrumentation.js", path18.join("src", "instrumentation.ts")].map((c) => path18.join(root, c)).find((f2) => fs22.existsSync(f2));
20590
+ const existing = ["instrumentation.ts", "instrumentation.js", path18.join("src", "instrumentation.ts")].map((c) => path18.join(root, c)).find((f2) => fs23.existsSync(f2));
20309
20591
  const body = `import { register as apiblaze } from "apiblaze/sidecar";
20310
20592
 
20311
20593
  export function register() {
@@ -20313,18 +20595,18 @@ export function register() {
20313
20595
  }
20314
20596
  `;
20315
20597
  if (!existing) {
20316
- fs22.writeFileSync(path18.join(root, "instrumentation.ts"), body);
20598
+ fs23.writeFileSync(path18.join(root, "instrumentation.ts"), body);
20317
20599
  return "created";
20318
20600
  }
20319
- const cur = fs22.readFileSync(existing, "utf8");
20601
+ const cur = fs23.readFileSync(existing, "utf8");
20320
20602
  if (cur.includes("apiblaze/sidecar")) return "present";
20321
20603
  if (/export\s+function\s+register\s*\(/.test(cur)) {
20322
- fs22.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
20604
+ fs23.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
20323
20605
  ` + cur.replace(/export\s+function\s+register\s*\(\s*\)\s*\{/, (m) => `${m}
20324
20606
  apiblaze();`));
20325
20607
  return "patched";
20326
20608
  }
20327
- fs22.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
20609
+ fs23.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
20328
20610
  ${cur}
20329
20611
  // call apiblaze() inside your register() export.
20330
20612
  `);
@@ -20407,16 +20689,16 @@ export default async function Page() {
20407
20689
  function generateInspector(root, router) {
20408
20690
  try {
20409
20691
  if (router === "pages") {
20410
- const dir2 = fs22.existsSync(path18.join(root, "src", "pages")) ? path18.join(root, "src", "pages") : path18.join(root, "pages");
20692
+ const dir2 = fs23.existsSync(path18.join(root, "src", "pages")) ? path18.join(root, "src", "pages") : path18.join(root, "pages");
20411
20693
  const f3 = path18.join(dir2, "abz-inspector.tsx");
20412
- fs22.writeFileSync(f3, INSPECTOR_PAGE);
20694
+ fs23.writeFileSync(f3, INSPECTOR_PAGE);
20413
20695
  return path18.relative(root, f3);
20414
20696
  }
20415
- const base2 = fs22.existsSync(path18.join(root, "src", "app")) ? path18.join(root, "src", "app") : path18.join(root, "app");
20697
+ const base2 = fs23.existsSync(path18.join(root, "src", "app")) ? path18.join(root, "src", "app") : path18.join(root, "app");
20416
20698
  const dir = path18.join(base2, "abz-inspector");
20417
- fs22.mkdirSync(dir, { recursive: true });
20699
+ fs23.mkdirSync(dir, { recursive: true });
20418
20700
  const f2 = path18.join(dir, "page.tsx");
20419
- fs22.writeFileSync(f2, INSPECTOR_PAGE);
20701
+ fs23.writeFileSync(f2, INSPECTOR_PAGE);
20420
20702
  return path18.relative(root, f2);
20421
20703
  } catch {
20422
20704
  return null;
@@ -22169,7 +22451,7 @@ program.command("login").description(`Authenticate with ${B.product}`).option("-
22169
22451
  process.exit(1);
22170
22452
  }
22171
22453
  });
22172
- program.command("create").description(`Put an app that is already DEPLOYED behind ${B.product}: a public URL and an MCP address for AI agents in front of your URL or OpenAPI spec. Included: API keys, sign-in, ownership rules, rate limits. No login needed. (App on this machine? Use \`dev\`.) Add --compliant to make it a Claude connector, or a ChatGPT plugin with --compliant chatgpt, that meets the directory's policies.`).option("--name <name>", B.cli === "mcpblaze" ? "MCP server name (becomes <name>-<tenant>.mcpblaze.com)" : "Proxy name (becomes <name>.abz.run)").option("--target <url|file>", "What to proxy \u2014 pass ANY of: a target server base URL (https://httpbin.org), a local OpenAPI file (./openapi.yaml), or a remote OpenAPI URL (https://acme.com/openapi.yaml). Spec inputs are detected automatically; routes, API version and environments then come from the spec").addOption(new import_commander.Option("--openapi <file|url>", "Deprecated alias \u2014 --target now detects spec files/URLs itself").hideHelp()).addOption(new import_commander.Option("--openapispec <file|url>", "Deprecated alias for --openapi").hideHelp()).option("--team <id|name>", "Team to create under (defaults to your active team)").option("--auth <type>", "Open ONE door only: api_key | oauth (sign in with GitHub, APIblaze-hosted) | none. Omitted = both doors.").option("--apikey", "API-key door only (no sign-in): the bootstrap keys are minted and printed; callers send X-API-Key, agents connect with a key.").option("--oauth [config]", `Sign-in door only (no API key). Bare = APIblaze-hosted GitHub sign-in. '{"iss","aud","jwks"}' = trust YOUR hosted login's JWTs. '{"provider","clientId","clientSecret"?}' = APIblaze-hosted login page with YOUR OAuth app (github \xB7 google \xB7 microsoft \xB7 facebook \xB7 auth0 \xB7 oidc); the secret is optional except for github and facebook. '{"provider":"oidc","issuer":"https://acme.okta.com/oauth2/default","clientId":"\u2026"}' = any OpenID Connect IdP by issuer (Okta \xB7 Entra \xB7 Keycloak \xB7 Cognito \xB7 \u2026).`).option("--identified", "Require every call to identify its end user (X-End-User-Id from a backend key, or a login token \u2014 a client key alone cannot); unattributed calls are rejected").option("--iam", `Turn IAM on for the ${B.thingShort}'s tenant so users & groups apply to identified calls`).option("--apiversion <version>", `API version to create (e.g. 2.0.0). Creating a new version of a ${B.thingShort} you own adds a version to the existing project.`).option("--tenant <slug>", `Tenant to attach the ${B.thingShort} to (created if new). Omitted \u2192 your team's most-recently-used tenant.`).option("--product <slug>", "Product tag to group this project under in the portal (team-scoped; anonymous create). Defaults to your team's existing/placeholder tag.").option("--display-name <name>", "Human-friendly display name").option("--subdomain <slug>", "Explicit subdomain (defaults to --name)").option("--config <file>", "JSON file with the full request body (anonymous create): requests_auth, login providers + client/server token types, scopes, callback URLs, etc. See apiblaze_anonymous.yaml. Flags override its fields.").option("-y, --yes", "Skip the confirmation prompt").option("--auto", `Zero prompts, start to finish: generates the ${B.thingShort} name when --name is omitted (from the target host, salted), takes every confirmation as yes, auto-picks the tenant. Implies --yes.`).option("--new-session", "Start a fresh anonymous session (do not group with prior anonymous creates)").option("--json", "Output machine-readable JSON (non-interactive)").option("--compliant [directories...]", `Directory-ready in one command, to the letter of that directory's policies: create the ${B.thingShort} from --target (an OpenAPI URL/file), apply the safe fixes, set up the custom MCP domain (--domain, one CNAME), ask for every listing answer with the spec's proposal pre-filled (cut to the portal's limits), create the reviewer account, write the submission packet. Bare = claude; or claude \xB7 chatgpt \xB7 all. Ends with the compliance table: every rule the listing satisfies and the policy page it comes from. Same as \`${B.cli} connector <spec> --compliant \u2026\`.`).addOption(new import_commander.Option("--policy [directories...]", "Older spelling of --compliant").hideHelp()).option("--domain <host>", "With --compliant: the MCP server's custom hostname (mcp.<your domain>); asked for when missing, suggested from the spec").option("--wait <minutes>", "With --compliant: how long to wait for the custom domain to activate once its CNAME is in place (default 15; 0 = do not wait)").option("--auth-mode <mode>", "With --compliant: how directory clients get in. signin (default) = OAuth sign-in on the connector, API keys alongside; open = no sign-in at all, for public read-only data (Anthropic allows it) \u2014 the API host keeps its own doors. Asked once when every tool is read-only.").option("--data-handling <mode>", "Compliant: whose API this is (own \xB7 proxied + --data-handling-statement \xB7 third-party); never pre-filled").option("--data-handling-statement <text>", "Compliant, proxied: the permission you hold").option("--health-data <yes|no>", "Compliant: the tools handle health data (never pre-filled)").option("--sponsored-content <yes|no>", "Compliant: tool results carry sponsored content (never pre-filled)").option("--no-exercise", 'Compliant: skip the "Run every tool now?" step').option("--no-email", "Compliant: no email at the end of the run").option("--directory-ready", "Make it directory-ready for the Claude and ChatGPT directories: OAuth door, custom-domain checks, listing fields; the readiness report runs after the create and a blocker exits 1").option("--privacy-url <url>", "Directory listing: public https privacy policy").option("--support <email|url>", "Directory listing: support contact").option("--docs-url <url>", "Directory listing: public documentation URL (the developer portal by default)").option("--prompt <text>", "Directory listing: an example prompt (repeat the flag; three are needed)", collect, []).option("--server-name <name>", 'Directory listing: the name (2\u201340 chars, no "MCP"/"Plugin" suffix)').option("--tagline <text>", "Directory listing: one-line tagline").option("--description <text>", "Directory listing: description").option("--icon-url <url>", "Directory listing: square icon (https)").option("--vuln-contact <email|url>", "Directory listing: vulnerability-disclosure contact").option("--attest <id>", "Attest (repeatable): guidelines \xB7 own-backend \xB7 no-financial \xB7 no-genai-media \xB7 no-prompt-injection \xB7 no-conversation-data \xB7 public-docs (Claude's seven) \xB7 no-digital-goods \xB7 content-13-no-ads \xB7 no-restricted-data (ChatGPT)", collect, []).option("--allowed-origin <origin>", "Directory mode: an extra browser origin allowed on the MCP host (repeatable)", collect, []).option("--openai-challenge <token>", "ChatGPT: the domain-verification token the OpenAI portal shows under Plugins \u2192 Connect MCP server").action(async (opts) => {
22454
+ program.command("create").description(`Put an app that is already DEPLOYED behind ${B.product}: a public URL and an MCP address for AI agents in front of your URL or OpenAPI spec. Included: API keys, sign-in, ownership rules, rate limits. No login needed. (App on this machine? Use \`dev\`.) Add --compliant to make it a Claude connector, or a ChatGPT plugin with --compliant chatgpt, that meets the directory's policies.`).option("--name <name>", B.cli === "mcpblaze" ? "MCP server name (becomes <name>-<tenant>.mcpblaze.com)" : "Proxy name (becomes <name>.abz.run)").option("--target <url|file>", "What to proxy \u2014 pass ANY of: a target server base URL (https://httpbin.org), a local OpenAPI file (./openapi.yaml), or a remote OpenAPI URL (https://acme.com/openapi.yaml). Spec inputs are detected automatically; routes, API version and environments then come from the spec").addOption(new import_commander.Option("--openapi <file|url>", "Deprecated alias \u2014 --target now detects spec files/URLs itself").hideHelp()).addOption(new import_commander.Option("--openapispec <file|url>", "Deprecated alias for --openapi").hideHelp()).option("--team <id|name>", "Team to create under (defaults to your active team)").option("--auth <type>", "Open ONE door only: api_key | oauth (sign in with GitHub, APIblaze-hosted) | none. Omitted = both doors.").option("--apikey", "API-key door only (no sign-in): the bootstrap keys are minted and printed; callers send X-API-Key, agents connect with a key.").option("--oauth [config]", `Sign-in door only (no API key). Bare = APIblaze-hosted GitHub sign-in. '{"iss","aud","jwks"}' = trust YOUR hosted login's JWTs. '{"provider","clientId","clientSecret"?}' = APIblaze-hosted login page with YOUR OAuth app (github \xB7 google \xB7 microsoft \xB7 facebook \xB7 auth0 \xB7 oidc); the secret is optional except for github and facebook. '{"provider":"oidc","issuer":"https://acme.okta.com/oauth2/default","clientId":"\u2026"}' = any OpenID Connect IdP by issuer (Okta \xB7 Entra \xB7 Keycloak \xB7 Cognito \xB7 \u2026).`).option("--identified", "Require every call to identify its end user (X-End-User-Id from a backend key, or a login token \u2014 a client key alone cannot); unattributed calls are rejected").option("--iam", `Turn IAM on for the ${B.thingShort}'s tenant so users & groups apply to identified calls`).option("--apiversion <version>", `API version to create (e.g. 2.0.0). Creating a new version of a ${B.thingShort} you own adds a version to the existing project.`).option("--tenant <slug>", `Tenant to attach the ${B.thingShort} to (created if new). Omitted \u2192 your team's most-recently-used tenant.`).option("--product <slug>", "Product tag to group this project under in the portal (team-scoped; anonymous create). Defaults to your team's existing/placeholder tag.").option("--display-name <name>", "Human-friendly display name").option("--subdomain <slug>", "Explicit subdomain (defaults to --name)").option("--config <file>", "JSON file with the full request body (anonymous create): requests_auth, login providers + client/server token types, scopes, callback URLs, etc. See apiblaze_anonymous.yaml. Flags override its fields.").option("-y, --yes", "Skip the confirmation prompt").option("--auto", `Zero prompts, start to finish: generates the ${B.thingShort} name when --name is omitted (from the target host, salted), takes every confirmation as yes, auto-picks the tenant. Implies --yes.`).option("--new-session", "Start a fresh anonymous session (do not group with prior anonymous creates)").option("--json", "Output machine-readable JSON (non-interactive)").option("--compliant [directories...]", `Directory-ready in one command, to the letter of that directory's policies: create the ${B.thingShort} from --target (an OpenAPI URL/file), apply the safe fixes, set up the custom MCP domain (--domain, one CNAME), ask for every listing answer with the spec's proposal pre-filled (cut to the portal's limits), create the reviewer account, write the submission packet. Bare = claude; or claude \xB7 chatgpt \xB7 all. Ends with the compliance table: every rule the listing satisfies and the policy page it comes from. Same as \`${B.cli} connector <spec> --compliant \u2026\`.`).addOption(new import_commander.Option("--policy [directories...]", "Older spelling of --compliant").hideHelp()).option("--domain <host>", "With --compliant: the MCP server's custom hostname (mcp.<your domain>); asked for when missing, suggested from the spec").option("--wait <minutes>", "With --compliant: how long to wait for the custom domain to activate once its CNAME is in place (default 15; 0 = do not wait)").option("--auth-mode <mode>", "With --compliant: how directory clients get in. signin (default) = OAuth sign-in on the connector, API keys alongside; open = no sign-in at all, for public read-only data (Anthropic allows it) \u2014 the API host keeps its own doors. Asked once when every tool is read-only.").option("--data-handling <mode>", "Compliant: whose API this is (own \xB7 proxied + --data-handling-statement \xB7 third-party); never pre-filled").option("--data-handling-statement <text>", "Compliant, proxied: the permission you hold").option("--health-data <yes|no>", "Compliant: the tools handle health data (never pre-filled)").option("--sponsored-content <yes|no>", "Compliant: tool results carry sponsored content (never pre-filled)").option("--no-exercise", 'Compliant: skip the "Run every tool now?" step').option("--seed", "Sign-in mode: when a read returns nothing for the reviewer, create a few records through the create tools and keep them (asked when omitted)").option("--no-seed", "Never seed the reviewer account").option("--no-email", "Compliant: no email at the end of the run").option("--directory-ready", "Make it directory-ready for the Claude and ChatGPT directories: OAuth door, custom-domain checks, listing fields; the readiness report runs after the create and a blocker exits 1").option("--privacy-url <url>", "Directory listing: public https privacy policy").option("--support <email|url>", "Directory listing: support contact").option("--docs-url <url>", "Directory listing: public documentation URL (the developer portal by default)").option("--prompt <text>", "Directory listing: an example prompt (repeat the flag; three are needed)", collect, []).option("--server-name <name>", 'Directory listing: the name (2\u201340 chars, no "MCP"/"Plugin" suffix)').option("--tagline <text>", "Directory listing: one-line tagline").option("--description <text>", "Directory listing: description").option("--icon-url <url>", "Directory listing: square icon (https)").option("--slug <slug>", "Claude: the listing slug (lowercase-with-hyphens; permanent once published)").option("--high-risk <list>", "Claude policy 1A: none, or any of legal,healthcare,insurance,finance").option("--open-world <yes|no>", "Every tool: reaches public or open-ended data (yes) or only the signed-in account's (no); asked once when omitted").option("--no-watch", "Do not turn on the nightly re-check email when the listing is green").option("--no-wait-login", "Sign-in mode: do not wait for a Claude sign-in before the verdict").option("--mcp-app", "Claude: the listing is an MCP App (the host then advertises the UI extension; needs 3\u20135 --screenshot)").option("--no-mcp-app", "Claude: a plain connector (default)").option("--screenshot <url=prompt>", "Claude MCP App: a carousel screenshot (PNG \u2265 1000 px wide) and the prompt that produced it (repeatable, 3\u20135)", collect, []).option("--vuln-contact <email|url>", "Directory listing: vulnerability-disclosure contact").option("--attest <id>", "Attest (repeatable): guidelines \xB7 own-backend \xB7 no-financial \xB7 no-genai-media \xB7 no-prompt-injection \xB7 no-conversation-data \xB7 public-docs (Claude's seven) \xB7 no-digital-goods \xB7 content-13-no-ads \xB7 no-restricted-data (ChatGPT)", collect, []).option("--allowed-origin <origin>", "Directory mode: an extra browser origin allowed on the MCP host (repeatable)", collect, []).option("--openai-challenge <token>", "ChatGPT: the domain-verification token the OpenAI portal shows under Plugins \u2192 Connect MCP server").action(async (opts) => {
22173
22455
  try {
22174
22456
  const compliant = opts.compliant ?? opts.policy;
22175
22457
  if (compliant !== void 0 && compliant !== false) {
@@ -22189,11 +22471,11 @@ function collect(value, previous) {
22189
22471
  return [...previous ?? [], value];
22190
22472
  }
22191
22473
  program.command("check").description(`Readiness report for the Claude and ChatGPT directories (--directory-ready): what passes, every blocker with its fix, and the ${B.thingShort}'s published URL. Exit 1 while a blocker remains.`).argument("[project]", `The ${B.thingShort} (project name)`).option("--directory-ready", "The directory readiness checks (the only mode today)").option("--compliant [directory]", "Which directory's policies to check against: bare = claude; or claude \xB7 chatgpt \xB7 all (repeat or comma-separate)", collect, []).addOption(new import_commander.Option("--policy <directory>", "Older spelling of --compliant").hideHelp()).addOption(new import_commander.Option("--platform <list>", "Older spelling of --compliant").hideHelp()).option("--stage <stage>", "create (relaxed: docs URL is a warning) \xB7 ready (the submission gate, default)").option("--refresh-snapshot", "Re-take the listing snapshot (after you edited the listing in the portals)").option("--no-probe", "Skip the live probes from this machine (report what the server can see)").option("--team <id|name>", "Team the project is in (defaults to your active team)").option("--apiversion <version>", "API version (defaults to the project's)").option("--json", "Print the raw report JSON (report, rendered text, target)").action(action((project, opts) => runCheck(project, { ...opts, noProbe: opts.probe === false })));
22192
- var connector = program.command("connector").description(`A compliant Claude connector (--compliant chatgpt: a ChatGPT plugin), directory-ready in one command: from a project or straight from an OpenAPI URL \u2014 creates the ${B.thingShort} if needed, applies the safe fixes, sets up the custom MCP domain (one CNAME), pre-fills every listing answer from the spec, creates the reviewer account, and writes the submission packet when every check passes (you submit it to Anthropic and OpenAI yourself)`).argument("[project-or-spec]", `The ${B.thingShort} (project name), or an OpenAPI URL/file to create a directory-ready ${B.thingShort} from first`).option("--domain <host>", "Custom hostname for the MCP server (mcp.<your domain>); asked for when missing, suggested from the spec").option("--wait <minutes>", "How long to wait for the custom domain to activate once its CNAME is in place (default 15; 0 = do not wait)").option("--auth-mode <mode>", "How directory clients get in: signin (default) = OAuth sign-in on the connector, API keys alongside; open = no sign-in at all, for public read-only data (Anthropic allows it) \u2014 the API host keeps its own doors. Asked once when every tool is read-only.").option("--name <name>", `When creating from a spec: the ${B.thingShort} name (default: derived from the spec host)`).option("--privacy-url <url>", "Public https privacy policy").option("--support <email|url>", "Support contact").option("--docs-url <url>", "Public documentation URL").option("--prompt <text>", "An example prompt (repeatable; three are needed)", collect, []).option("--server-name <name>", 'Listing name (2\u201340 chars, no "MCP"/"Plugin" suffix)').option("--tagline <text>", "One-line tagline").option("--description <text>", "Description").option("--icon-url <url>", "Square icon (https)").option("--vuln-contact <email|url>", "Vulnerability-disclosure contact").option("--attest <id>", "Attest (repeatable): guidelines \xB7 own-backend \xB7 no-financial \xB7 no-genai-media \xB7 no-prompt-injection \xB7 no-conversation-data \xB7 public-docs (Claude's seven) \xB7 no-digital-goods \xB7 content-13-no-ads \xB7 no-restricted-data (ChatGPT). Alone, this only records the attestation.", collect, []).option("--allowed-origin <origin>", "An extra browser origin allowed on the MCP host (repeatable)", collect, []).option("--openai-challenge <token>", "ChatGPT: the domain-verification token the OpenAI portal shows under Plugins \u2192 Connect MCP server; served verbatim at /.well-known/openai-apps-challenge on your MCP domain").option("--data-handling <mode>", "Whose API this is (portal step 7): own \xB7 proxied (add --data-handling-statement) \xB7 third-party. Never pre-filled.").option("--data-handling-statement <text>", "For proxied: the permission you hold (who granted what, and when)").option("--health-data <yes|no>", "The tools handle health data (never pre-filled; yes adds the human-in-the-loop note to the packet)").option("--sponsored-content <yes|no>", "Tool results carry sponsored content (never pre-filled; yes is an unsupported use case)").option("--website-url <url>", "OpenAI's form: the public website (https)").option("--support-url <url>", "OpenAI's form: the support page (https; a URL, not an email)").option("--terms-url <url>", "OpenAI's form: the terms of service page (https)").option("--developer-name <name>", "OpenAI's form: the developer name (\u2264 80; the directory shows your verified identity)").option("--chatgpt-category <name>", "OpenAI's form: one of Productivity \xB7 Creativity \xB7 Developer Tools \xB7 Business & Operations \xB7 Data & Analytics \xB7 Communication \xB7 Education & Research \xB7 Security \xB7 Finance \xB7 Healthcare \xB7 Travel \xB7 Entertainment \xB7 Other").option("--demo-recording-url <url>", "OpenAI's form: the demo recording (required for MCP review)").option("--release-notes <text>", "OpenAI's form: release notes for this submission").option("--short-description <text>", "OpenAI's form: the 30-character subtitle (Claude's tagline stays \u2264 200)").option("--countries <codes>", "OpenAI's form: ISO country codes, comma-separated (default: everywhere)", (v) => v.split(",").map((s) => s.trim()).filter(Boolean)).option("--no-exercise", 'Skip the "Run every tool now?" step at the end').option("--no-email", "No email at the end of the run").option("--full", "Run the full flow even when only --attest was given").option("--compliant [directory]", "Which directory's policies this run must satisfy: bare = claude; or claude \xB7 chatgpt \xB7 all (repeat or comma-separate)", collect, []).addOption(new import_commander.Option("--policy <directory>", "Older spelling of --compliant").hideHelp()).addOption(new import_commander.Option("--platform <list>", "Older spelling of --compliant").hideHelp()).option("--out <dir>", "Where to write connector-packet-<project>.md/.json (default: current directory)").option("-y, --yes", "No questions: apply the flags, report, exit 1 on a blocker").option("--team <id|name>", "Team the project is in (defaults to your active team)").option("--apiversion <version>", "API version (defaults to the project's)").option("--json", "Output machine-readable JSON").action(action(async (project, opts) => {
22474
+ var connector = program.command("connector").description(`A compliant Claude connector (--compliant chatgpt: a ChatGPT plugin), directory-ready in one command: from a project or straight from an OpenAPI URL \u2014 creates the ${B.thingShort} if needed, applies the safe fixes, sets up the custom MCP domain (one CNAME), pre-fills every listing answer from the spec, creates the reviewer account, and writes the submission packet when every check passes (you submit it to Anthropic and OpenAI yourself)`).argument("[project-or-spec]", `The ${B.thingShort} (project name), or an OpenAPI URL/file to create a directory-ready ${B.thingShort} from first`).option("--domain <host>", "Custom hostname for the MCP server (mcp.<your domain>); asked for when missing, suggested from the spec").option("--wait <minutes>", "How long to wait for the custom domain to activate once its CNAME is in place (default 15; 0 = do not wait)").option("--auth-mode <mode>", "How directory clients get in: signin (default) = OAuth sign-in on the connector, API keys alongside; open = no sign-in at all, for public read-only data (Anthropic allows it) \u2014 the API host keeps its own doors. Asked once when every tool is read-only.").option("--name <name>", `When creating from a spec: the ${B.thingShort} name (default: derived from the spec host)`).option("--privacy-url <url>", "Public https privacy policy").option("--support <email|url>", "Support contact").option("--docs-url <url>", "Public documentation URL").option("--prompt <text>", "An example prompt (repeatable; three are needed)", collect, []).option("--server-name <name>", 'Listing name (2\u201340 chars, no "MCP"/"Plugin" suffix)').option("--tagline <text>", "One-line tagline").option("--description <text>", "Description").option("--icon-url <url>", "Square icon (https)").option("--slug <slug>", "Claude: the listing slug (lowercase-with-hyphens; permanent once published)").option("--high-risk <list>", "Claude policy 1A: none, or any of legal,healthcare,insurance,finance").option("--mcp-app", "Claude: the listing is an MCP App (needs 3\u20135 --screenshot)").option("--no-mcp-app", "Claude: a plain connector (default)").option("--screenshot <url=prompt>", "Claude MCP App: a carousel screenshot and the prompt that produced it (repeatable, 3\u20135)", collect, []).option("--vuln-contact <email|url>", "Vulnerability-disclosure contact").option("--attest <id>", "Attest (repeatable): guidelines \xB7 own-backend \xB7 no-financial \xB7 no-genai-media \xB7 no-prompt-injection \xB7 no-conversation-data \xB7 public-docs (Claude's seven) \xB7 no-digital-goods \xB7 content-13-no-ads \xB7 no-restricted-data (ChatGPT). Alone, this only records the attestation.", collect, []).option("--allowed-origin <origin>", "An extra browser origin allowed on the MCP host (repeatable)", collect, []).option("--openai-challenge <token>", "ChatGPT: the domain-verification token the OpenAI portal shows under Plugins \u2192 Connect MCP server; served verbatim at /.well-known/openai-apps-challenge on your MCP domain").option("--data-handling <mode>", "Whose API this is (portal step 7): own \xB7 proxied (add --data-handling-statement) \xB7 third-party. Never pre-filled.").option("--data-handling-statement <text>", "For proxied: the permission you hold (who granted what, and when)").option("--health-data <yes|no>", "The tools handle health data (never pre-filled; yes adds the human-in-the-loop note to the packet)").option("--sponsored-content <yes|no>", "Tool results carry sponsored content (never pre-filled; yes is an unsupported use case)").option("--website-url <url>", "OpenAI's form: the public website (https)").option("--support-url <url>", "OpenAI's form: the support page (https; a URL, not an email)").option("--terms-url <url>", "OpenAI's form: the terms of service page (https)").option("--developer-name <name>", "OpenAI's form: the developer name (\u2264 80; the directory shows your verified identity)").option("--chatgpt-category <name>", "OpenAI's form: one of Productivity \xB7 Creativity \xB7 Developer Tools \xB7 Business & Operations \xB7 Data & Analytics \xB7 Communication \xB7 Education & Research \xB7 Security \xB7 Finance \xB7 Healthcare \xB7 Travel \xB7 Entertainment \xB7 Other").option("--demo-recording-url <url>", "OpenAI's form: the demo recording (required for MCP review)").option("--release-notes <text>", "OpenAI's form: release notes for this submission").option("--short-description <text>", "OpenAI's form: the 30-character subtitle (Claude's tagline stays \u2264 200)").option("--countries <codes>", "OpenAI's form: ISO country codes, comma-separated (default: everywhere)", (v) => v.split(",").map((s) => s.trim()).filter(Boolean)).option("--no-exercise", 'Skip the "Run every tool now?" step at the end').option("--seed", "Sign-in mode: when a read returns nothing for the reviewer, create a few records through the create tools and keep them (asked when omitted)").option("--no-seed", "Never seed the reviewer account").option("--no-email", "No email at the end of the run").option("--full", "Run the full flow even when only --attest was given").option("--compliant [directory]", "Which directory's policies this run must satisfy: bare = claude; or claude \xB7 chatgpt \xB7 all (repeat or comma-separate)", collect, []).addOption(new import_commander.Option("--policy <directory>", "Older spelling of --compliant").hideHelp()).addOption(new import_commander.Option("--platform <list>", "Older spelling of --compliant").hideHelp()).option("--out <dir>", "Where to write connector-packet-<project>.md/.json (default: current directory)").option("-y, --yes", "No questions: apply the flags, report, exit 1 on a blocker").option("--team <id|name>", "Team the project is in (defaults to your active team)").option("--apiversion <version>", "API version (defaults to the project's)").option("--json", "Output machine-readable JSON").action(action(async (project, opts) => {
22193
22475
  if (!project) throw new Error(`Which ${B.thingShort}? ${B.cli} connector <project> \xB7 ${B.cli} connector https://\u2026/openapi.yaml \xB7 ${B.cli} connector reviewer <project> \xB7 ${B.cli} connector exercise <project> \xB7 ${B.cli} connector answers <project>`);
22194
22476
  await runConnector(project, opts);
22195
22477
  }));
22196
- connector.command("exercise").description("Call every tool the way a reviewer would, as the reviewer account (open mode: as you): reads with a valid and an invalid argument, writes only on ids the run created and only after a y, then the three listing prompts through Claude Code when it is installed. The gateway records the outcomes; the report reads them.").argument("<project>", `The ${B.thingShort} (project name)`).option("--empty-ok <tool>", 'A read-only tool whose correct answer is "nothing" (repeatable)', collect, []).option("--no-claude", "Skip the Claude Code pass").option("--no-email", "No email at the end of the run").option("-y, --yes", "No questions: writes are not exercised (they need a y), the rest runs").option("--team <id|name>", "Team the project is in (defaults to your active team)").option("--apiversion <version>", "API version (defaults to the project's)").option("--json", "Output machine-readable JSON").action(action(async (project, opts) => {
22478
+ connector.command("exercise").description("Call every tool the way a reviewer would, as the reviewer account (open mode: as you): reads with a valid and an invalid argument, writes only on ids the run created and only after a y, then the three listing prompts through Claude Code when it is installed. The gateway records the outcomes; the report reads them.").argument("<project>", `The ${B.thingShort} (project name)`).option("--empty-ok <tool>", 'A read-only tool whose correct answer is "nothing" (repeatable)', collect, []).option("--no-claude", "Skip the Claude Code pass").option("--seed", "Sign-in mode: when a read returns nothing for the reviewer, create records through the create tools and keep them").option("--no-seed", "Never seed the reviewer account").option("--no-email", "No email at the end of the run").option("-y, --yes", "No questions: writes are not exercised (they need a y), the rest runs").option("--team <id|name>", "Team the project is in (defaults to your active team)").option("--apiversion <version>", "API version (defaults to the project's)").option("--json", "Output machine-readable JSON").action(action(async (project, opts) => {
22197
22479
  await runConnectorExercise(project, { ...withConnectorOpts(opts), noClaude: opts.claude === false, noEmail: opts.email === false });
22198
22480
  }));
22199
22481
  connector.command("docs").description('Generate the three documents a listing needs \u2014 the public page "Using <name> in Claude" (served at <portal>/connector), the reviewer instructions and the portal answers \u2014 on your Claude Code from the facts of the connector; lint them, store them, point the docs URL at the page. Edit them on the dashboard any time.').argument("<project>", `The ${B.thingShort} (project name)`).option("--from <file>", "Paste back a JSON answer produced elsewhere (see --prompt-out)").option("--prompt-out <file>", "Write the prompt + facts to a file instead of running Claude Code").option("--team <id|name>", "Team the project is in (defaults to your active team)").option("--apiversion <version>", "API version (defaults to the project's)").option("--json", "Output machine-readable JSON").action(action((project, opts) => runConnectorDocs(project, withConnectorOpts(opts))));
@@ -22226,7 +22508,7 @@ mcpCommon(mcpPrompts.command("set").description("Add or replace a prompt from a
22226
22508
  mcpCommon(mcpPrompts.command("rm").description("Remove a prompt").argument("<project>", "Project name or id").argument("<name>", "Prompt name")).action(action((project, name, opts) => runMcpPrompts("rm", project, name, opts)));
22227
22509
  var mcpOps = mcpCmd.command("ops").description("Per-tool behavior. Default from the HTTP method: GET/HEAD/OPTIONS read-only, PUT/DELETE idempotent, DELETE destructive + asks to confirm");
22228
22510
  mcpCommon(mcpOps.command("list").description("Every tool with its effective behavior (overrides marked)").argument("<project>", "Project name or id")).action(action((project, opts) => runMcpOps("list", project, void 0, opts)));
22229
- mcpCommon(mcpOps.command("set").description("Override one tool").argument("<project>", "Project name or id").argument("<tool>", "Tool name").option("--title <text>", "Human label clients and directories show (default: the OpenAPI summary, else the name spelled out)").option("--read-only", "Safe to call, changes nothing (e.g. a POST search)").option("--no-read-only", "Not read-only").option("--destructive", "Changes or deletes data (e.g. a GET with side effects)").option("--no-destructive", "Not destructive").option("--idempotent", "Repeating it has no extra effect").option("--no-idempotent", "Not idempotent").option("--confirm", "Ask the user to confirm before running").option("--no-confirm", "Never ask to confirm").option("--always-async", "Return a task handle immediately (long jobs)").option("--no-always-async", "Wait for the result (default)")).action(action((project, tool, opts) => runMcpOps("set", project, tool, opts)));
22511
+ mcpCommon(mcpOps.command("set").description("Override one tool").argument("<project>", "Project name or id").argument("<tool>", "Tool name").option("--title <text>", "Human label clients and directories show (default: the OpenAPI summary, else the name spelled out)").option("--read-only", "Safe to call, changes nothing (e.g. a POST search)").option("--no-read-only", "Not read-only").option("--destructive", "Changes or deletes data (e.g. a GET with side effects)").option("--no-destructive", "Not destructive").option("--idempotent", "Repeating it has no extra effect").option("--no-idempotent", "Not idempotent").option("--confirm", "Ask the user to confirm before running").option("--no-confirm", "Never ask to confirm").option("--always-async", "Return a task handle immediately (long jobs)").option("--no-always-async", "Wait for the result (default)").option("--open-world", "Reaches public or open-ended data (web pages, third-party records); the directories read openWorldHint").option("--no-open-world", "Only the signed-in account's own data")).action(action((project, tool, opts) => runMcpOps("set", project, tool, opts)));
22230
22512
  mcpCommon(mcpOps.command("reset").description("Back to the automatic default for one tool").argument("<project>", "Project name or id").argument("<tool>", "Tool name")).action(action((project, tool, opts) => runMcpOps("reset", project, tool, opts)));
22231
22513
  mcpCommon(mcpCmd.command("ui").description("Print a tool's MCP Apps UI resource (ui://{project}/{tool}) \u2014 the automatic viewer").argument("<project>", "Project name or id").argument("<tool>", "Tool name")).action(action((project, tool, opts) => runMcpUi(project, tool, opts)));
22232
22514
  program.command("apichat [project]").alias("chat").description("Turn any API into a chat: point at an OpenAPI spec \u2014 or chat an EXISTING proxy by name (no login needed)").option("--target <url|file>", "What to chat with \u2014 pass ANY of: a target server base URL (spec auto-discovered at /openapi.json etc.), a local OpenAPI file (./openapi.yaml), or a remote OpenAPI URL (https://acme.com/openapi.yaml)").addOption(new import_commander.Option("--openapi <file|url>", "Deprecated alias \u2014 --target now detects spec files/URLs itself").hideHelp()).addOption(new import_commander.Option("--openapispec <file|url>", "Deprecated alias for --openapi").hideHelp()).option("--name <name>", "Proxy name (defaults to the target host)").option("--apiversion <version>", "API version to create (e.g. 1.0.0)").option("--environment <env>", "Environment to chat against (default: prod anonymous / dev logged-in)").option("--access <mode>", 'Who can call this API once connected (e.g. via Claude): "open" = anyone who signs in, "invite" = only you + emails you pre-approve. Default: invite when logged in, open when anonymous.').option("--target-auth-env <ENV_VAR>", "Read the upstream credential from this env var (CI-safe; required when there is no TTY and the API needs auth)").option("--force", "Proceed even if the API uses oauth2/openIdConnect target auth (you configure target auth yourself later)").option("-y, --yes", "Skip confirmation prompts").option("--tenant <slug>", "Tenant (consumer namespace: portal, login, users) for the new proxy; omit to be asked").option("--apikey <key>", "Use this API key for the proxy's door (api_key proxies). Without it, apichat detects the door and asks \u2014 or runs the consumer login for OAuth doors.").option("--xenduserid <id>", "Assert this end-user id (X-End-User-Id) \u2014 required by proxies with identified/pre-approved enforcement; you are asked for one when the proxy demands it.").option("--verbose", "Show the per-turn proxy curl trace (hidden by default)").option("-p, --prompt <question>", "One-shot question: answered through the external agent CLI after an MCP install, or by apichat itself (exits after answering when there is no TTY)").option("--install-mcp <cli>", "Install this proxy's MCP into an external agent CLI without asking: claude | codex. Also re-offers after an earlier decline.").option("--remove-mcp <cli>", "Disconnect this proxy from an external agent CLI (claude | codex): removes the MCP server and the standing instruction").action(action((project, opts) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apiblaze",
3
- "version": "0.21.69",
3
+ "version": "0.21.70",
4
4
  "description": "APIblaze CLI — Chat with your APIs, Manage your API keys, users and groups with the APIblaze serverless proxy",
5
5
  "keywords": [
6
6
  "apiblaze",