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.
- package/dist/index.js +473 -191
- 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.
|
|
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
|
-
|
|
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}
|
|
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
|
|
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
|
|
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(`
|
|
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)
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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) =>
|
|
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(
|
|
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 =
|
|
20248
|
-
const pagesDir =
|
|
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 =
|
|
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
|
-
|
|
20552
|
+
fs23.writeFileSync(p, next);
|
|
20271
20553
|
return had ? "rotated" : "created";
|
|
20272
20554
|
}
|
|
20273
20555
|
function installSidecarPackage(root) {
|
|
20274
|
-
if (
|
|
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) =>
|
|
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 =
|
|
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 =
|
|
20584
|
+
c = fs23.readFileSync(p, "utf8");
|
|
20303
20585
|
} catch {
|
|
20304
20586
|
}
|
|
20305
|
-
if (!/^\.env\.local$/m.test(c) && !/^\.env\*/m.test(c))
|
|
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) =>
|
|
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
|
-
|
|
20598
|
+
fs23.writeFileSync(path18.join(root, "instrumentation.ts"), body);
|
|
20317
20599
|
return "created";
|
|
20318
20600
|
}
|
|
20319
|
-
const cur =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
20694
|
+
fs23.writeFileSync(f3, INSPECTOR_PAGE);
|
|
20413
20695
|
return path18.relative(root, f3);
|
|
20414
20696
|
}
|
|
20415
|
-
const base2 =
|
|
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
|
-
|
|
20699
|
+
fs23.mkdirSync(dir, { recursive: true });
|
|
20418
20700
|
const f2 = path18.join(dir, "page.tsx");
|
|
20419
|
-
|
|
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) => {
|