apiblaze 0.21.68 → 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 +1100 -279
- 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
|
|
|
@@ -445,8 +445,8 @@ function dualAuth(opts = {}) {
|
|
|
445
445
|
function bothDoorsBody(opts = {}) {
|
|
446
446
|
return { auth_type: "api_key", requests_auth: dualAuth(opts), login: GITHUB_LOGIN };
|
|
447
447
|
}
|
|
448
|
-
async function apiFetch(
|
|
449
|
-
const url = `${DASHBOARD_BASE2}${
|
|
448
|
+
async function apiFetch(path20, options = {}, auth) {
|
|
449
|
+
const url = `${DASHBOARD_BASE2}${path20}`;
|
|
450
450
|
const res = await fetch(url, {
|
|
451
451
|
...options,
|
|
452
452
|
headers: {
|
|
@@ -472,12 +472,12 @@ async function apiFetch(path19, options = {}, auth) {
|
|
|
472
472
|
}
|
|
473
473
|
return res.json();
|
|
474
474
|
}
|
|
475
|
-
async function agentCall(
|
|
475
|
+
async function agentCall(path20, method, body) {
|
|
476
476
|
const token = getAccessToken();
|
|
477
477
|
const res = await fetch(`${DASHBOARD_BASE2}/api/cli/agents`, {
|
|
478
478
|
method: "POST",
|
|
479
479
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
480
|
-
body: JSON.stringify({ path:
|
|
480
|
+
body: JSON.stringify({ path: path20, method, body })
|
|
481
481
|
});
|
|
482
482
|
let data = null;
|
|
483
483
|
try {
|
|
@@ -962,11 +962,11 @@ function decodeJwt(token) {
|
|
|
962
962
|
return null;
|
|
963
963
|
}
|
|
964
964
|
}
|
|
965
|
-
function maskPath(
|
|
966
|
-
const q =
|
|
967
|
-
if (q < 0) return
|
|
968
|
-
const base2 =
|
|
969
|
-
const query =
|
|
965
|
+
function maskPath(path20) {
|
|
966
|
+
const q = path20.indexOf("?");
|
|
967
|
+
if (q < 0) return path20;
|
|
968
|
+
const base2 = path20.slice(0, q);
|
|
969
|
+
const query = path20.slice(q + 1);
|
|
970
970
|
const masked = query.split("&").map((pair) => {
|
|
971
971
|
const eq = pair.indexOf("=");
|
|
972
972
|
if (eq < 0) return pair;
|
|
@@ -1263,8 +1263,8 @@ function pageQuery(cursor, limit = PAGE_LIMIT) {
|
|
|
1263
1263
|
if (cursor) qs.set("cursor", cursor);
|
|
1264
1264
|
return qs.toString();
|
|
1265
1265
|
}
|
|
1266
|
-
function withPage(
|
|
1267
|
-
return `${
|
|
1266
|
+
function withPage(path20, cursor, limit = PAGE_LIMIT) {
|
|
1267
|
+
return `${path20}${path20.includes("?") ? "&" : "?"}${pageQuery(cursor, limit)}`;
|
|
1268
1268
|
}
|
|
1269
1269
|
var PAGE_LIMIT, MAX_HOPS;
|
|
1270
1270
|
var init_paging = __esm({
|
|
@@ -2002,12 +2002,12 @@ var init_env_local = __esm({
|
|
|
2002
2002
|
});
|
|
2003
2003
|
|
|
2004
2004
|
// src/lib/preapproval.ts
|
|
2005
|
-
async function iamCall(tenant2,
|
|
2005
|
+
async function iamCall(tenant2, path20, method = "GET", body) {
|
|
2006
2006
|
const token = getAccessToken();
|
|
2007
2007
|
const res = await fetch(`${DASHBOARD_BASE5}/api/cli/iam`, {
|
|
2008
2008
|
method: "POST",
|
|
2009
2009
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
2010
|
-
body: JSON.stringify({ tenant: tenant2, path:
|
|
2010
|
+
body: JSON.stringify({ tenant: tenant2, path: path20, method, body })
|
|
2011
2011
|
});
|
|
2012
2012
|
const data = await res.json().catch(() => ({}));
|
|
2013
2013
|
if (!res.ok) throw new ApiError(res.status, data?.details ?? data?.error ?? res.statusText, data);
|
|
@@ -3456,13 +3456,13 @@ async function ensureConsumerLogin(teamId, tenant2, version2) {
|
|
|
3456
3456
|
console.log(` ${import_chalk15.default.green("\u2714")} Signed in as${creds.email ? ` ${import_chalk15.default.bold(creds.email)}` : " a consumer"} on ${tenant2}.`);
|
|
3457
3457
|
return creds;
|
|
3458
3458
|
}
|
|
3459
|
-
async function cpPost(anon,
|
|
3459
|
+
async function cpPost(anon, path20, body, summary) {
|
|
3460
3460
|
if (anon) {
|
|
3461
3461
|
const cred = loadAnonCred();
|
|
3462
3462
|
if (!cred) throw new Error("Anonymous workspace credential missing.");
|
|
3463
|
-
return cpFetch(cred.cp_key,
|
|
3463
|
+
return cpFetch(cred.cp_key, path20, { method: "POST", body: JSON.stringify(body) });
|
|
3464
3464
|
}
|
|
3465
|
-
return admin({ method: "POST", path:
|
|
3465
|
+
return admin({ method: "POST", path: path20, body, summary });
|
|
3466
3466
|
}
|
|
3467
3467
|
async function provision(spec2, target2, opts) {
|
|
3468
3468
|
const loggedIn = !!loadCredentials();
|
|
@@ -5165,12 +5165,148 @@ 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, {
|
|
5171
5306
|
PROBE_TIMEOUT_MS: () => PROBE_TIMEOUT_MS,
|
|
5172
5307
|
asMetadataUrl: () => asMetadataUrl,
|
|
5173
|
-
probeDirectoryTarget: () => probeDirectoryTarget
|
|
5308
|
+
probeDirectoryTarget: () => probeDirectoryTarget,
|
|
5309
|
+
probeUserinfo: () => probeUserinfo
|
|
5174
5310
|
});
|
|
5175
5311
|
async function probe(url, init, deps) {
|
|
5176
5312
|
const f2 = deps.fetch ?? fetch;
|
|
@@ -5238,7 +5374,9 @@ async function probeDirectoryTarget(target2, deps = {}) {
|
|
|
5238
5374
|
}
|
|
5239
5375
|
})() ? redirected : null,
|
|
5240
5376
|
sseServed: sse.status == null ? null : sse.status === 200,
|
|
5241
|
-
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
|
|
5242
5380
|
};
|
|
5243
5381
|
const prmUrl = `${origin.origin}/.well-known/oauth-protected-resource${origin.pathname.replace(/\/$/, "")}`;
|
|
5244
5382
|
const prm = await probe(prmUrl, { method: "GET" }, deps);
|
|
@@ -5286,6 +5424,45 @@ async function probeDirectoryTarget(target2, deps = {}) {
|
|
|
5286
5424
|
const name = (target2.serverName ?? "").trim();
|
|
5287
5425
|
out.metadata = { ...out.metadata ?? {}, docsStatus: p.status, docsContainsName: p.status === 200 && name ? body.toLowerCase().includes(name.toLowerCase()) : null };
|
|
5288
5426
|
}
|
|
5427
|
+
for (const [url, key] of [[target2.websiteUrl, "websiteStatus"], [target2.supportUrl, "supportStatus"], [target2.termsUrl, "termsStatus"]]) {
|
|
5428
|
+
if (!url || !/^https?:\/\//i.test(url)) continue;
|
|
5429
|
+
let p = await probe(url, { method: "GET", headers: { accept: "text/html, */*;q=0.5" } }, deps);
|
|
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);
|
|
5431
|
+
out.metadata = { ...out.metadata ?? {}, [key]: p.status };
|
|
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
|
+
}
|
|
5289
5466
|
const ch = await probe(`${origin.origin}/.well-known/openai-apps-challenge`, { method: "GET" }, deps);
|
|
5290
5467
|
out.openaiChallenge = { servedStatus: ch.status, servedMatches: null };
|
|
5291
5468
|
if (ch.status === 200 && typeof ch.json === "string") out.openaiChallenge.servedBody = ch.json.trim().slice(0, 128);
|
|
@@ -5294,17 +5471,25 @@ async function probeDirectoryTarget(target2, deps = {}) {
|
|
|
5294
5471
|
function asMetadataUrl(issuer) {
|
|
5295
5472
|
try {
|
|
5296
5473
|
const u = new URL(issuer);
|
|
5297
|
-
const
|
|
5298
|
-
return `${u.origin}/.well-known/oauth-authorization-server${
|
|
5474
|
+
const path20 = u.pathname.replace(/\/$/, "");
|
|
5475
|
+
return `${u.origin}/.well-known/oauth-authorization-server${path20}`;
|
|
5299
5476
|
} catch {
|
|
5300
5477
|
return `${issuer.replace(/\/$/, "")}/.well-known/oauth-authorization-server`;
|
|
5301
5478
|
}
|
|
5302
5479
|
}
|
|
5480
|
+
async function probeUserinfo(as, bearer, deps = {}) {
|
|
5481
|
+
const ep = typeof as?.userinfo_endpoint === "string" ? as.userinfo_endpoint : null;
|
|
5482
|
+
if (!ep || !bearer) return { userinfoStatus: null, userinfoEmailVerified: null };
|
|
5483
|
+
const p = await probe(ep, { method: "GET", headers: { authorization: `Bearer ${bearer}` } }, deps);
|
|
5484
|
+
const body = obj(p.json);
|
|
5485
|
+
return { userinfoStatus: p.status, userinfoEmailVerified: p.status === 200 ? body.email_verified === true : null };
|
|
5486
|
+
}
|
|
5303
5487
|
var import_promises, PROBE_TIMEOUT_MS, strArr, obj;
|
|
5304
5488
|
var init_directory_probes = __esm({
|
|
5305
5489
|
"src/lib/directory-probes.ts"() {
|
|
5306
5490
|
"use strict";
|
|
5307
5491
|
import_promises = __toESM(require("dns/promises"));
|
|
5492
|
+
init_zip();
|
|
5308
5493
|
PROBE_TIMEOUT_MS = 5e3;
|
|
5309
5494
|
strArr = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : null;
|
|
5310
5495
|
obj = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
@@ -5318,6 +5503,7 @@ __export(directory_exports, {
|
|
|
5318
5503
|
AUTH_MODES: () => AUTH_MODES,
|
|
5319
5504
|
DATA_HANDLING_MODES: () => DATA_HANDLING_MODES,
|
|
5320
5505
|
DESCRIPTION_MIN: () => DESCRIPTION_MIN,
|
|
5506
|
+
OPENAI_CATEGORIES: () => OPENAI_CATEGORIES,
|
|
5321
5507
|
PLATFORMS: () => PLATFORMS,
|
|
5322
5508
|
attestationId: () => attestationId,
|
|
5323
5509
|
blockerLines: () => blockerLines,
|
|
@@ -5325,12 +5511,14 @@ __export(directory_exports, {
|
|
|
5325
5511
|
fetchDirectoryReport: () => fetchDirectoryReport,
|
|
5326
5512
|
isApiError: () => isApiError,
|
|
5327
5513
|
isReady: () => isReady,
|
|
5514
|
+
listingOf: () => listingOf,
|
|
5328
5515
|
parseAuthMode: () => parseAuthMode,
|
|
5329
5516
|
parseCompliant: () => parseCompliant,
|
|
5330
5517
|
parseDataHandling: () => parseDataHandling,
|
|
5331
5518
|
parsePlatforms: () => parsePlatforms,
|
|
5332
5519
|
parseYesNo: () => parseYesNo,
|
|
5333
|
-
printReport: () => printReport
|
|
5520
|
+
printReport: () => printReport,
|
|
5521
|
+
writeJsonLine: () => writeJsonLine
|
|
5334
5522
|
});
|
|
5335
5523
|
function attestationId(flag) {
|
|
5336
5524
|
const f2 = flag.trim().toLowerCase();
|
|
@@ -5358,7 +5546,7 @@ function parseYesNo(flag, v) {
|
|
|
5358
5546
|
}
|
|
5359
5547
|
function directoryBodyFromFlags(f2) {
|
|
5360
5548
|
const m = {};
|
|
5361
|
-
for (const [flag, v] of [["--privacy-url", f2.privacyUrl], ["--docs-url", f2.docsUrl], ["--icon-url", f2.iconUrl]]) {
|
|
5549
|
+
for (const [flag, v] of [["--privacy-url", f2.privacyUrl], ["--docs-url", f2.docsUrl], ["--icon-url", f2.iconUrl], ["--website-url", f2.websiteUrl], ["--support-url", f2.supportUrl], ["--terms-url", f2.termsUrl], ["--demo-recording-url", f2.demoRecordingUrl]]) {
|
|
5362
5550
|
if (v && !/^https:\/\//i.test(v.trim())) throw new Error(`${flag} must be an https URL (got ${v.trim()}).`);
|
|
5363
5551
|
}
|
|
5364
5552
|
if (f2.privacyUrl) m.privacy_url = f2.privacyUrl;
|
|
@@ -5372,8 +5560,39 @@ function directoryBodyFromFlags(f2) {
|
|
|
5372
5560
|
if (f2.tagline) m.tagline = f2.tagline;
|
|
5373
5561
|
if (f2.description) m.description = f2.description;
|
|
5374
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
|
+
}
|
|
5375
5581
|
if (f2.vulnContact) m.vuln_contact = f2.vulnContact;
|
|
5376
5582
|
if (f2.allowedOrigin?.length) m.allowed_origins = f2.allowedOrigin.map((o) => o.trim()).filter(Boolean);
|
|
5583
|
+
if (f2.websiteUrl) m.website_url = f2.websiteUrl.trim();
|
|
5584
|
+
if (f2.supportUrl) m.support_url = f2.supportUrl.trim();
|
|
5585
|
+
if (f2.termsUrl) m.terms_url = f2.termsUrl.trim();
|
|
5586
|
+
if (f2.developerName) m.developer_name = f2.developerName.trim();
|
|
5587
|
+
if (f2.category) {
|
|
5588
|
+
const hit = OPENAI_CATEGORIES.find((c) => c.toLowerCase() === f2.category.trim().toLowerCase());
|
|
5589
|
+
if (!hit) throw new Error(`Unknown --category "${f2.category}". OpenAI's list: ${OPENAI_CATEGORIES.join(", ")}.`);
|
|
5590
|
+
m.category = hit;
|
|
5591
|
+
}
|
|
5592
|
+
if (f2.demoRecordingUrl) m.demo_recording_url = f2.demoRecordingUrl.trim();
|
|
5593
|
+
if (f2.releaseNotes) m.release_notes = f2.releaseNotes.trim();
|
|
5594
|
+
if (f2.shortDescription) m.short_description = f2.shortDescription.trim();
|
|
5595
|
+
if (f2.countries) m.countries = f2.countries.map((c) => c.trim().toUpperCase()).filter(Boolean);
|
|
5377
5596
|
const mode = parseAuthMode(f2.authMode);
|
|
5378
5597
|
if (mode) m.auth_mode = mode;
|
|
5379
5598
|
const dh = parseDataHandling(f2.dataHandling, f2.dataHandlingStatement);
|
|
@@ -5396,6 +5615,11 @@ function directoryBodyFromFlags(f2) {
|
|
|
5396
5615
|
}
|
|
5397
5616
|
return out;
|
|
5398
5617
|
}
|
|
5618
|
+
function listingOf(r) {
|
|
5619
|
+
const m = r?.target?.metadata ?? {};
|
|
5620
|
+
const s = (k) => typeof m[k] === "string" ? m[k] : null;
|
|
5621
|
+
return { privacyUrl: s("privacyUrl"), docsUrl: s("docsUrl"), serverName: s("serverName"), websiteUrl: s("websiteUrl"), supportUrl: s("supportUrl"), termsUrl: s("termsUrl") };
|
|
5622
|
+
}
|
|
5399
5623
|
function parseCompliant(policy, platform) {
|
|
5400
5624
|
if (platform && (policy === void 0 || policy === true)) return parsePlatforms(platform) ?? ["claude"];
|
|
5401
5625
|
if (policy === void 0 || policy === false || policy === true) return ["claude"];
|
|
@@ -5431,11 +5655,12 @@ async function fetchDirectoryReport(caller, proj2, opts = {}) {
|
|
|
5431
5655
|
first = await producer(caller, { method: "POST", path: reportPath(proj2), body: base2, summary: `Directory report for ${proj2.projectName}` });
|
|
5432
5656
|
}
|
|
5433
5657
|
const publishedUrl = opts.publishedUrl ?? first?.report?.project?.publishedUrl ?? null;
|
|
5434
|
-
const meta = first?.target?.metadata;
|
|
5658
|
+
const meta = first?.target?.metadata ?? opts.listing ?? void 0;
|
|
5435
5659
|
const privacyUrl = opts.privacyUrl ?? meta?.privacyUrl ?? null;
|
|
5436
|
-
if (publishedUrl) probes = await probeDirectoryTarget({ publishedUrl, privacyUrl, docsUrl: meta?.docsUrl ?? null, serverName: meta?.serverName ?? 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 });
|
|
5437
5661
|
else if (first && !opts.refreshSnapshot) return first;
|
|
5438
5662
|
}
|
|
5663
|
+
if (opts.extraProbes) probes = { ...probes ?? {}, ...opts.extraProbes, metadata: { ...probes?.metadata ?? {}, ...opts.extraProbes.metadata ?? {} } };
|
|
5439
5664
|
return producer(caller, {
|
|
5440
5665
|
method: "POST",
|
|
5441
5666
|
path: reportPath(proj2),
|
|
@@ -5447,9 +5672,12 @@ function isReady(r, platforms) {
|
|
|
5447
5672
|
const want = platforms ?? r.report.platformsRequested ?? [...PLATFORMS];
|
|
5448
5673
|
return want.every((p) => r.report.platforms[p]?.ready === true);
|
|
5449
5674
|
}
|
|
5675
|
+
function writeJsonLine(value) {
|
|
5676
|
+
import_node_fs.default.writeSync(1, JSON.stringify(value) + "\n");
|
|
5677
|
+
}
|
|
5450
5678
|
function printReport(r, json) {
|
|
5451
5679
|
if (json) {
|
|
5452
|
-
|
|
5680
|
+
writeJsonLine(r);
|
|
5453
5681
|
return;
|
|
5454
5682
|
}
|
|
5455
5683
|
console.log(r.rendered.text.replace(/<project>/g, r.report.project.id));
|
|
@@ -5463,10 +5691,11 @@ function blockerLines(r, platforms) {
|
|
|
5463
5691
|
function isApiError(e, status2) {
|
|
5464
5692
|
return e instanceof ApiError && (status2 === void 0 || e.status === status2);
|
|
5465
5693
|
}
|
|
5466
|
-
var import_chalk17, PLATFORMS, ATTESTATIONS, 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;
|
|
5467
5695
|
var init_directory = __esm({
|
|
5468
5696
|
"src/lib/directory.ts"() {
|
|
5469
5697
|
"use strict";
|
|
5698
|
+
import_node_fs = __toESM(require("fs"));
|
|
5470
5699
|
import_chalk17 = __toESM(require("chalk"));
|
|
5471
5700
|
init_caller();
|
|
5472
5701
|
init_directory_probes();
|
|
@@ -5484,8 +5713,11 @@ var init_directory = __esm({
|
|
|
5484
5713
|
// ChatGPT's guidelines add these three.
|
|
5485
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"] },
|
|
5486
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 },
|
|
5487
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"] }
|
|
5488
5719
|
];
|
|
5720
|
+
OPENAI_CATEGORIES = ["Productivity", "Creativity", "Developer Tools", "Business & Operations", "Data & Analytics", "Communication", "Education & Research", "Security", "Finance", "Healthcare", "Travel", "Entertainment", "Other"];
|
|
5489
5721
|
AUTH_MODES = ["signin", "open"];
|
|
5490
5722
|
DATA_HANDLING_MODES = ["own", "proxied", "third-party"];
|
|
5491
5723
|
reportPath = (proj2) => `/projects/${encodeURIComponent(proj2.projectId)}/${encodeURIComponent(proj2.apiVersion)}/directory-report`;
|
|
@@ -7490,6 +7722,58 @@ function mcpConfigDocument(url, bearer, runId2) {
|
|
|
7490
7722
|
const headers = { ...bearer ? { Authorization: `Bearer ${bearer}` } : {}, ...runId2 ? { "apiblaze-directory-run": runId2 } : {} };
|
|
7491
7723
|
return JSON.stringify({ mcpServers: { abz: { type: "http", url, ...Object.keys(headers).length ? { headers } : {} } } }, null, 2) + "\n";
|
|
7492
7724
|
}
|
|
7725
|
+
function codexRecipe(o) {
|
|
7726
|
+
const toml = (s) => JSON.stringify(s);
|
|
7727
|
+
return [
|
|
7728
|
+
"exec",
|
|
7729
|
+
"--json",
|
|
7730
|
+
"--ephemeral",
|
|
7731
|
+
"--ignore-user-config",
|
|
7732
|
+
"--skip-git-repo-check",
|
|
7733
|
+
// Built-ins off, as for Claude Code: with web search or a browser on, Codex "looks at the site" itself
|
|
7734
|
+
// instead of calling the tool (seen live 2026-11-04: "the site wouldn't load through my browsing tools").
|
|
7735
|
+
"-c",
|
|
7736
|
+
'web_search="disabled"',
|
|
7737
|
+
"--disable",
|
|
7738
|
+
"browser_use",
|
|
7739
|
+
"--disable",
|
|
7740
|
+
"browser_use_external",
|
|
7741
|
+
"--disable",
|
|
7742
|
+
"in_app_browser",
|
|
7743
|
+
...o.cwd ? ["-C", o.cwd] : [],
|
|
7744
|
+
"-c",
|
|
7745
|
+
`mcp_servers.abz.url=${toml(o.serverUrl)}`,
|
|
7746
|
+
"-c",
|
|
7747
|
+
`mcp_servers.abz.http_headers={"apiblaze-directory-run"=${toml(o.runId)}}`,
|
|
7748
|
+
...o.bearerEnvVar ? ["-c", `mcp_servers.abz.bearer_token_env_var=${toml(o.bearerEnvVar)}`] : [],
|
|
7749
|
+
"-c",
|
|
7750
|
+
"mcp_servers.abz.required=true",
|
|
7751
|
+
"-c",
|
|
7752
|
+
'mcp_servers.abz.default_tools_approval_mode="approve"',
|
|
7753
|
+
"-c",
|
|
7754
|
+
`mcp_servers.abz.enabled_tools=[${o.enabledTools.map(toml).join(",")}]`,
|
|
7755
|
+
o.prompt
|
|
7756
|
+
];
|
|
7757
|
+
}
|
|
7758
|
+
function toolCallsInCodexOutput(stdout) {
|
|
7759
|
+
const names = /* @__PURE__ */ new Set();
|
|
7760
|
+
for (const line of stdout.split("\n")) {
|
|
7761
|
+
const s = line.trim();
|
|
7762
|
+
if (!s.startsWith("{")) continue;
|
|
7763
|
+
let ev;
|
|
7764
|
+
try {
|
|
7765
|
+
ev = JSON.parse(s);
|
|
7766
|
+
} catch {
|
|
7767
|
+
continue;
|
|
7768
|
+
}
|
|
7769
|
+
const item = ev?.item ?? ev;
|
|
7770
|
+
if (item && item.type === "mcp_tool_call" && typeof item.tool === "string" && (item.server === void 0 || item.server === "abz")) names.add(item.tool);
|
|
7771
|
+
}
|
|
7772
|
+
return [...names];
|
|
7773
|
+
}
|
|
7774
|
+
function readOnlyToolNames(tools) {
|
|
7775
|
+
return tools.filter((t) => toolRole(t) === "read").map((t) => t.name);
|
|
7776
|
+
}
|
|
7493
7777
|
function toolCallsInClaudeOutput(stdout) {
|
|
7494
7778
|
const names = /* @__PURE__ */ new Set();
|
|
7495
7779
|
for (const line of stdout.split("\n")) {
|
|
@@ -7524,11 +7808,14 @@ var init_exercise = __esm({
|
|
|
7524
7808
|
var portal_drafts_exports = {};
|
|
7525
7809
|
__export(portal_drafts_exports, {
|
|
7526
7810
|
ANSWER_MIN: () => ANSWER_MIN,
|
|
7811
|
+
defaultNegativePrompts: () => defaultNegativePrompts,
|
|
7527
7812
|
describeResult: () => describeResult,
|
|
7813
|
+
draftChatgptTestCases: () => draftChatgptTestCases,
|
|
7528
7814
|
draftDescription: () => draftDescription,
|
|
7529
7815
|
draftMissing: () => draftMissing,
|
|
7530
7816
|
draftReviewerInstructions: () => draftReviewerInstructions,
|
|
7531
|
-
draftUseCases: () => draftUseCases
|
|
7817
|
+
draftUseCases: () => draftUseCases,
|
|
7818
|
+
humanizeShape: () => humanizeShape
|
|
7532
7819
|
});
|
|
7533
7820
|
function describeResult(text) {
|
|
7534
7821
|
const t = (text ?? "").trim();
|
|
@@ -7545,6 +7832,21 @@ function describeResult(text) {
|
|
|
7545
7832
|
const head = t.replace(/\s+/g, " ").slice(0, 80);
|
|
7546
7833
|
return `a text answer ("${head}${t.length > 80 ? "\u2026" : ""}")`;
|
|
7547
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
|
+
}
|
|
7548
7850
|
function argsText(args) {
|
|
7549
7851
|
if (!args || !Object.keys(args).length) return "no arguments";
|
|
7550
7852
|
return Object.entries(args).map(([k, v]) => `${k}=${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
|
|
@@ -7570,7 +7872,7 @@ function draftUseCases(i) {
|
|
|
7570
7872
|
for (const g of groups.values()) {
|
|
7571
7873
|
const row = i.rows.find((r) => r.tool === g.tools[0].name && r.valid === "ok");
|
|
7572
7874
|
const which = g.prompts.length === list.length && list.length > 1 ? "Each of these" : g.prompts.map(short).join(", ");
|
|
7573
|
-
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`}.`);
|
|
7574
7876
|
}
|
|
7575
7877
|
const uncalled = i.tools.filter((t) => !Object.values(i.promptTools).some((ns) => ns.includes(t.name)));
|
|
7576
7878
|
if (uncalled.length) parts.push(`Also available: ${uncalled.map((t) => `${titleOf(t)}${(t.description ?? "").trim() ? ` (${(t.description ?? "").trim().replace(/\.$/, "")})` : ""}`).join("; ")}.`);
|
|
@@ -7579,20 +7881,21 @@ function draftUseCases(i) {
|
|
|
7579
7881
|
}
|
|
7580
7882
|
function draftReviewerInstructions(i) {
|
|
7581
7883
|
const lines = [];
|
|
7582
|
-
|
|
7583
|
-
lines.push(
|
|
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)";
|
|
7885
|
+
lines.push(`Add ${i.publishedUrl} as a custom connector in ${where}.`);
|
|
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.`);
|
|
7584
7887
|
const seen = /* @__PURE__ */ new Set();
|
|
7585
7888
|
for (const p of i.prompts) {
|
|
7586
7889
|
const called = toolsCalledBy(i, p);
|
|
7587
7890
|
if (!called.length) continue;
|
|
7588
7891
|
const row = i.rows.find((r) => r.tool === called[0].name && r.valid === "ok");
|
|
7589
7892
|
called.forEach((t) => seen.add(t.name));
|
|
7590
|
-
lines.push(`Ask: "${p}" \u2014
|
|
7893
|
+
lines.push(`Ask: "${p}" \u2014 uses ${called.map(titleOf).join(" and ")}${row ? `; expect ${humanizeShape(shapeOf(row))}` : ""}.`);
|
|
7591
7894
|
}
|
|
7592
7895
|
for (const r of i.rows) {
|
|
7593
7896
|
if (seen.has(r.tool) || r.valid !== "ok") continue;
|
|
7594
7897
|
const t = i.tools.find((x) => x.name === r.tool) ?? { name: r.tool };
|
|
7595
|
-
lines.push(`Ask
|
|
7898
|
+
lines.push(`Ask it to ${titleOf(t).replace(/^./, (c) => c.toLowerCase())} (${argsText(r.args)}); expect ${humanizeShape(shapeOf(r))}.`);
|
|
7596
7899
|
}
|
|
7597
7900
|
const errs = i.rows.filter((r) => r.invalid === "error" && r.invalidText);
|
|
7598
7901
|
for (const r of errs.slice(0, 2)) {
|
|
@@ -7617,7 +7920,7 @@ function draftDescription(i) {
|
|
|
7617
7920
|
const working = i.prompts.filter((p) => (i.promptTools[p] ?? []).length);
|
|
7618
7921
|
if (working.length) parts.push(`Ask it things like ${working.map((p) => `"${p}"`).join(", ")}.`);
|
|
7619
7922
|
const ok = i.rows.find((r) => r.valid === "ok");
|
|
7620
|
-
if (ok) parts.push(`
|
|
7923
|
+
if (ok) parts.push(`Answers come back as ${humanizeShape(shapeOf(ok))}, explained in plain words.`);
|
|
7621
7924
|
parts.push(readOnly(i) ? "Every tool only reads; nothing is created or changed." : "Writes change data in the signed-in account only.");
|
|
7622
7925
|
return parts.join(" ");
|
|
7623
7926
|
}
|
|
@@ -7629,6 +7932,40 @@ function draftMissing(i, current) {
|
|
|
7629
7932
|
if (len(current.reviewer_instructions) < ANSWER_MIN.reviewer_instructions) out.push({ key: "reviewer_instructions", label: "Reviewer instructions", draft: draftReviewerInstructions(i), current: (current.reviewer_instructions ?? "").trim() });
|
|
7630
7933
|
return out;
|
|
7631
7934
|
}
|
|
7935
|
+
function draftChatgptTestCases(i) {
|
|
7936
|
+
const view = i.codexPromptTools && Object.keys(i.codexPromptTools).length ? i.codexPromptTools : i.promptTools;
|
|
7937
|
+
const positive2 = [];
|
|
7938
|
+
for (const p of i.prompts) {
|
|
7939
|
+
const called = (view[p] ?? []).filter((n) => i.tools.some((t) => t.name === n));
|
|
7940
|
+
if (!called.length || positive2.length >= 5) continue;
|
|
7941
|
+
const tool = i.tools.find((t) => t.name === called[0]) ?? { name: called[0] };
|
|
7942
|
+
const row = i.rows.find((r) => r.tool === called[0] && r.valid === "ok");
|
|
7943
|
+
positive2.push({
|
|
7944
|
+
description: `${titleOf(tool)}: ${p.length > 60 ? `${p.slice(0, 57)}\u2026` : p}`,
|
|
7945
|
+
prompt: p,
|
|
7946
|
+
tools_triggered: called[0],
|
|
7947
|
+
expected_behavior: row ? `The assistant calls ${titleOf(tool)} and answers from ${shapeOf(row)}.` : `The assistant calls ${titleOf(tool)} and answers from its result.`
|
|
7948
|
+
});
|
|
7949
|
+
}
|
|
7950
|
+
const negative = (i.negativePrompts ?? []).slice(0, 3).map((n) => ({ description: n.description, prompt: n.prompt }));
|
|
7951
|
+
return { positive: positive2, negative };
|
|
7952
|
+
}
|
|
7953
|
+
function defaultNegativePrompts(i) {
|
|
7954
|
+
const name = (i.serverName ?? "it").trim();
|
|
7955
|
+
const allRead = i.tools.every((t) => /^(get|head)$/i.test(t.method ?? "get"));
|
|
7956
|
+
if (i.open) {
|
|
7957
|
+
return [
|
|
7958
|
+
{ prompt: `Delete everything ${name} knows about me`, description: "There is no write tool and no account: the assistant says it cannot change or delete anything." },
|
|
7959
|
+
{ prompt: `Sign me in to my ${name} account`, description: "The connector takes no sign-in (public data): the assistant explains there is no account to sign in to." },
|
|
7960
|
+
{ prompt: "Book me a table for two tonight", description: "Off topic for this connector: no tool fits, so none is called." }
|
|
7961
|
+
];
|
|
7962
|
+
}
|
|
7963
|
+
return [
|
|
7964
|
+
{ prompt: "Show me every record in the whole system, for every customer", description: "Cross-account data: the rules only expose the signed-in account, so the assistant refuses or shows only the user's own." },
|
|
7965
|
+
{ prompt: "Pay for it with my card ending 4242", description: "No payment tool exists and card data must not be collected: the assistant refuses." },
|
|
7966
|
+
{ prompt: allRead ? "Change my account email to someone else's" : "Book me a flight to Rome", description: allRead ? "Every tool is read-only: nothing can be changed, so no tool is called." : "Off topic for this connector: no tool fits, so none is called." }
|
|
7967
|
+
];
|
|
7968
|
+
}
|
|
7632
7969
|
var ANSWER_MIN, titleOf, shapeOf, readOnly;
|
|
7633
7970
|
var init_portal_drafts = __esm({
|
|
7634
7971
|
"src/lib/portal-drafts.ts"() {
|
|
@@ -7646,11 +7983,28 @@ __export(connector_exercise_exports, {
|
|
|
7646
7983
|
DASHBOARD_BASE: () => DASHBOARD_BASE8,
|
|
7647
7984
|
RUN_HEADER: () => RUN_HEADER,
|
|
7648
7985
|
RUN_META_KEY: () => RUN_META_KEY,
|
|
7986
|
+
clientsFor: () => clientsFor,
|
|
7649
7987
|
createPromptTester: () => createPromptTester,
|
|
7988
|
+
detectCodex: () => detectCodex,
|
|
7989
|
+
looksEmpty: () => looksEmpty,
|
|
7650
7990
|
reportLink: () => reportLink,
|
|
7651
7991
|
runConnectorAnswers: () => runConnectorAnswers,
|
|
7652
7992
|
runConnectorExercise: () => runConnectorExercise
|
|
7653
7993
|
});
|
|
7994
|
+
function clientsFor(platforms) {
|
|
7995
|
+
const p = platforms?.length ? platforms : ["claude", "chatgpt"];
|
|
7996
|
+
const out = [];
|
|
7997
|
+
if (p.includes("claude")) out.push("claude");
|
|
7998
|
+
if (p.includes("chatgpt")) out.push("codex");
|
|
7999
|
+
return out;
|
|
8000
|
+
}
|
|
8001
|
+
function detectCodex(spawn5 = import_child_process5.spawnSync) {
|
|
8002
|
+
const v = spawn5("codex", ["--version"], { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], timeout: 15e3 });
|
|
8003
|
+
if (v.error || v.status !== 0 || !/codex/i.test(String(v.stdout ?? ""))) return { installed: false, loggedIn: false };
|
|
8004
|
+
const st = spawn5("codex", ["login", "status"], { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], timeout: 15e3 });
|
|
8005
|
+
const text = `${st.stdout ?? ""}${st.stderr ?? ""}`;
|
|
8006
|
+
return { installed: true, loggedIn: st.status === 0 && !/not logged in/i.test(text) };
|
|
8007
|
+
}
|
|
7654
8008
|
function reportLink(projectId, apiVersion = "1.0.0") {
|
|
7655
8009
|
return `${DASHBOARD_BASE8}/directory/${encodeURIComponent(projectId)}/${encodeURIComponent(apiVersion)}`;
|
|
7656
8010
|
}
|
|
@@ -7660,6 +8014,16 @@ async function confirm(message, interactive2) {
|
|
|
7660
8014
|
const a = await inquirer3.prompt([{ type: "confirm", name: "go", message, default: false }]);
|
|
7661
8015
|
return a.go;
|
|
7662
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
|
+
}
|
|
7663
8027
|
async function runConnectorExercise(project, opts = {}) {
|
|
7664
8028
|
const caller = await requireCaller();
|
|
7665
8029
|
const { teamId } = await resolveActingTeam(caller, opts.team);
|
|
@@ -7691,17 +8055,22 @@ async function runConnectorExercise(project, opts = {}) {
|
|
|
7691
8055
|
let asMeta = null;
|
|
7692
8056
|
let configDir = null;
|
|
7693
8057
|
let reviewerUsername = null;
|
|
8058
|
+
let userinfo = null;
|
|
8059
|
+
const seeded = {};
|
|
7694
8060
|
try {
|
|
7695
8061
|
if (!open) {
|
|
7696
8062
|
const packet = await producer(caller, { method: "GET", path: `${projectPath(proj2)}/directory-packet?probe=0&force=1`, summary: "Reviewer account (for the login)" });
|
|
7697
8063
|
const acct = packet.json?.test_account ?? null;
|
|
7698
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.`);
|
|
7699
8065
|
const login = await headlessPasswordLogin({ mcpUrl: publishedUrl, username: acct.username, password: acct.password }, deps);
|
|
7700
|
-
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
|
+
}
|
|
7701
8070
|
bearer = login.accessToken;
|
|
7702
8071
|
asMeta = login.as;
|
|
7703
8072
|
reviewerUsername = acct.username;
|
|
7704
|
-
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`));
|
|
7705
8074
|
} else {
|
|
7706
8075
|
say(import_chalk26.default.dim(" open mode: no sign-in; this run's calls count as yours"));
|
|
7707
8076
|
}
|
|
@@ -7709,6 +8078,7 @@ async function runConnectorExercise(project, opts = {}) {
|
|
|
7709
8078
|
const headers = { [RUN_HEADER]: runId2 };
|
|
7710
8079
|
const call3 = (name, args) => mcpCall(publishedUrl, "tools/call", { name, arguments: args, _meta: { [RUN_META_KEY]: runId2 } }, { bearer, headers, userAgent: ua }, deps);
|
|
7711
8080
|
const created = {};
|
|
8081
|
+
const emptyReads = [];
|
|
7712
8082
|
const examples = { ...target2.metadata?.toolExamples ?? {} };
|
|
7713
8083
|
const learned = {};
|
|
7714
8084
|
const prompts = (target2.metadata?.examplePrompts ?? []).filter((p) => typeof p === "string");
|
|
@@ -7751,6 +8121,7 @@ async function runConnectorExercise(project, opts = {}) {
|
|
|
7751
8121
|
row.validText = redactHead(v.text);
|
|
7752
8122
|
row.args = args;
|
|
7753
8123
|
if (v.outcome === "ok") row.resultShape = describeResult(v.text);
|
|
8124
|
+
if (role === "read" && v.outcome === "ok" && looksEmpty(parseResultJson(v.text))) emptyReads.push(t);
|
|
7754
8125
|
if (v.outcome === "ok" && role === "create") {
|
|
7755
8126
|
const id = extractCreatedId(parseResultJson(v.text));
|
|
7756
8127
|
if (id) {
|
|
@@ -7773,35 +8144,99 @@ async function runConnectorExercise(project, opts = {}) {
|
|
|
7773
8144
|
await producer(caller, { method: "PATCH", path: projectPath(proj2), body: { directory_metadata: { tool_examples: learned } }, summary: "Remember the working arguments" });
|
|
7774
8145
|
say(import_chalk26.default.green(` \u2713 remembered the working arguments for ${Object.keys(learned).join(", ")} (used on every run from now on)`));
|
|
7775
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
|
+
}
|
|
8187
|
+
const clients = clientsFor(opts.platforms);
|
|
7776
8188
|
const claudeInstalled = deps.claudeInstalled ?? detectExternalClis().some((c) => c.kind === "claude");
|
|
7777
|
-
|
|
7778
|
-
|
|
7779
|
-
|
|
7780
|
-
|
|
7781
|
-
|
|
7782
|
-
|
|
7783
|
-
|
|
7784
|
-
|
|
7785
|
-
|
|
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 };
|
|
8190
|
+
const proven = opts.provenPromptNotes ?? [];
|
|
8191
|
+
const provenFor = (client) => new Map(proven.filter((n) => n.kind === "prompt" && (n.client ?? "claude") === client).map((n) => [n.prompt.trim(), n]));
|
|
8192
|
+
const passPrompts = (opts.promptsOnly ?? target2.metadata?.examplePrompts ?? []).filter((p) => typeof p === "string" && p.trim()).slice(0, 5);
|
|
8193
|
+
const negatives = (target2.metadata?.portalAnswers?.chatgpt_test_cases?.negative ?? []).map((c) => c.prompt).filter((p) => typeof p === "string" && p.trim()).slice(0, 3);
|
|
8194
|
+
const readOnly2 = readOnlyToolNames(tools);
|
|
8195
|
+
const requiredCount = (t) => Object.values(t.inputProperties ?? {}).filter((p) => p.required).length;
|
|
8196
|
+
const firstRead = [...tools].filter((t) => toolRole(t) === "read").sort((a, b) => requiredCount(a) - requiredCount(b))[0] ?? tools[0];
|
|
8197
|
+
const spawn5 = deps.spawn ?? import_child_process5.spawnSync;
|
|
8198
|
+
for (const client of clients) {
|
|
8199
|
+
if (opts.noClaude && client === "claude") continue;
|
|
8200
|
+
if (client === "claude" && !claudeInstalled) {
|
|
8201
|
+
say(import_chalk26.default.dim(" Claude Code is not installed here: the Claude pass is skipped (TESTED_FROM_CLAUDE and EXAMPLE_PROMPTS_WORK stay open with their instruction)."));
|
|
8202
|
+
continue;
|
|
8203
|
+
}
|
|
8204
|
+
if (client === "codex" && !(codex.installed && codex.loggedIn)) {
|
|
8205
|
+
say(import_chalk26.default.dim(` Codex CLI is ${codex.installed ? "not logged in" : "not installed"} here: the Codex pass is skipped (TESTED_FROM_OPENAI_CLIENT, EXAMPLE_PROMPTS_WORK_OPENAI and NEGATIVE_PROMPTS_HOLD stay open with their instruction).${codex.installed ? " Run `codex login` once." : " Install it: npm i -g @openai/codex, then `codex login`."}`));
|
|
8206
|
+
continue;
|
|
8207
|
+
}
|
|
8208
|
+
const already = provenFor(client);
|
|
8209
|
+
for (const p of passPrompts) {
|
|
8210
|
+
const n = already.get(p.trim());
|
|
8211
|
+
if (n) notes.push({ ...n });
|
|
8212
|
+
}
|
|
8213
|
+
if (opts.promptsOnly && client === "codex") for (const n of proven.filter((x) => x.kind === "negative" && x.client === "codex")) notes.push({ ...n });
|
|
7786
8214
|
const runs = [
|
|
7787
|
-
...opts.promptsOnly ? [] : [{ prompt: `Use the ${firstRead.title ?? firstRead.name} tool once and summarise what it returned in one sentence.`,
|
|
7788
|
-
...
|
|
8215
|
+
...opts.promptsOnly ? [] : [{ prompt: `Use the ${firstRead.title ?? firstRead.name} tool once and summarise what it returned in one sentence.`, kind: "tool" }],
|
|
8216
|
+
...passPrompts.filter((p) => !already.has(p.trim())).map((p) => ({ prompt: p, kind: "prompt" })),
|
|
8217
|
+
...client === "codex" && !opts.promptsOnly ? negatives.map((p) => ({ prompt: p, kind: "negative" })) : []
|
|
7789
8218
|
];
|
|
7790
|
-
|
|
7791
|
-
|
|
7792
|
-
|
|
8219
|
+
const label3 = client === "claude" ? "Claude Code" : "Codex CLI";
|
|
8220
|
+
if (already.size) say(import_chalk26.default.dim(` ${already.size} prompt${already.size > 1 ? "s" : ""} already run through ${label3} as ${already.size > 1 ? "they were" : "it was"} typed`));
|
|
8221
|
+
if (!runs.length) continue;
|
|
8222
|
+
say(import_chalk26.default.dim(` ${label3} pass: ${runs.length} one-shot${runs.length > 1 ? "s" : ""} (10\u201325 s each; read-only tools only)`));
|
|
8223
|
+
let cfgPath = null;
|
|
8224
|
+
if (client === "claude") {
|
|
8225
|
+
configDir = configDir ?? import_fs5.default.mkdtempSync(import_path4.default.join(deps.tmpdir ?? import_os2.default.tmpdir(), "abz-exercise-"));
|
|
8226
|
+
import_fs5.default.chmodSync(configDir, 448);
|
|
8227
|
+
cfgPath = import_path4.default.join(configDir, "mcp.json");
|
|
8228
|
+
import_fs5.default.writeFileSync(cfgPath, mcpConfigDocument(publishedUrl, bearer, runId2), { mode: 384 });
|
|
7793
8229
|
}
|
|
7794
|
-
if (proven.size) say(import_chalk26.default.dim(` ${proven.size} prompt${proven.size > 1 ? "s" : ""} already run through Claude Code as ${proven.size > 1 ? "they were" : "it was"} typed`));
|
|
7795
|
-
say(import_chalk26.default.dim(` Claude Code pass: ${runs.length} one-shot${runs.length > 1 ? "s" : ""} (10\u201325 s each)`));
|
|
7796
8230
|
for (const x of runs) {
|
|
7797
|
-
const
|
|
7798
|
-
|
|
7799
|
-
|
|
7800
|
-
|
|
8231
|
+
const allowed = x.kind === "tool" ? [firstRead.name] : readOnly2;
|
|
8232
|
+
const { calls, status: status2 } = client === "claude" ? claudeOneShot2(spawn5, cfgPath, x.prompt, allowed.map((n) => `mcp__abz__${n}`)) : codexOneShot2(spawn5, { prompt: x.prompt, serverUrl: publishedUrl, runId: runId2, enabledTools: allowed, bearer, cwd: deps.tmpdir });
|
|
8233
|
+
if (x.kind === "tool") notes.push({ kind: "claude-tool", client, tool: firstRead.name, tool_called: calls.length > 0 });
|
|
8234
|
+
else notes.push({ kind: x.kind, prompt: x.prompt, tool_called: calls.length > 0, tools: calls, client });
|
|
8235
|
+
const good = x.kind === "negative" ? calls.length === 0 : calls.length > 0;
|
|
8236
|
+
say(` ${good ? import_chalk26.default.green("\u2713") : import_chalk26.default.yellow("\u2013")} ${client}: "${x.prompt.slice(0, 60)}" \u2192 ${calls.length ? `called ${calls.join(", ")}` : "no tool call"}${x.kind === "negative" ? calls.length ? import_chalk26.default.yellow(" (a negative prompt must hold)") : import_chalk26.default.dim(" (held)") : ""}${status2 !== 0 && status2 != null ? import_chalk26.default.dim(` (exit ${status2})`) : ""}`);
|
|
7801
8237
|
}
|
|
7802
|
-
} else if (!opts.noClaude) {
|
|
7803
|
-
say(import_chalk26.default.dim(" Claude Code is not installed here: the Claude pass is skipped (TESTED_FROM_CLAUDE and EXAMPLE_PROMPTS_WORK stay open with their instruction)."));
|
|
7804
8238
|
}
|
|
8239
|
+
if (bearer && asMeta) userinfo = await probeUserinfo(asMeta, bearer, deps);
|
|
7805
8240
|
} finally {
|
|
7806
8241
|
if (configDir) {
|
|
7807
8242
|
try {
|
|
@@ -7817,15 +8252,29 @@ async function runConnectorExercise(project, opts = {}) {
|
|
|
7817
8252
|
const verdicts = r.report.verdicts ?? null;
|
|
7818
8253
|
const closed = await producer(caller, { method: "PATCH", path: `${projectPath(proj2)}/directory-runs/${encodeURIComponent(runId2)}`, body: { verdicts, scores: verdicts?.groups ?? null, notes, runner: "cli-direct", ...opts.platforms?.length ? { platforms: opts.platforms } : {}, ...opts.noEmail ? { email: false } : {} }, summary: "Close the exercise run" });
|
|
7819
8254
|
const promptTools = {};
|
|
7820
|
-
|
|
8255
|
+
const codexPromptTools = {};
|
|
8256
|
+
const failedByClient = {};
|
|
8257
|
+
for (const n of notes) {
|
|
8258
|
+
if (n.kind !== "prompt" || typeof n.prompt !== "string") continue;
|
|
8259
|
+
const tools2 = Array.isArray(n.tools) ? n.tools : [];
|
|
8260
|
+
const client = n.client ?? "claude";
|
|
8261
|
+
if (client === "codex") codexPromptTools[n.prompt] = tools2;
|
|
8262
|
+
if (!promptTools[n.prompt]?.length) promptTools[n.prompt] = tools2;
|
|
8263
|
+
if (n.tool_called === false) failedByClient[n.prompt] = [...failedByClient[n.prompt] ?? [], client];
|
|
8264
|
+
}
|
|
7821
8265
|
const summary = {
|
|
7822
|
-
failedPrompts:
|
|
8266
|
+
failedPrompts: Object.keys(failedByClient),
|
|
8267
|
+
failedByClient,
|
|
7823
8268
|
rows,
|
|
7824
8269
|
promptTools,
|
|
8270
|
+
codexPromptTools,
|
|
7825
8271
|
publishedUrl,
|
|
7826
8272
|
open,
|
|
7827
8273
|
reviewerUsername,
|
|
7828
|
-
tools
|
|
8274
|
+
tools,
|
|
8275
|
+
userinfo,
|
|
8276
|
+
seeded,
|
|
8277
|
+
notes: notes.filter((n) => (n.kind === "prompt" || n.kind === "negative") && typeof n.prompt === "string")
|
|
7829
8278
|
};
|
|
7830
8279
|
if (json) {
|
|
7831
8280
|
process.stdout.write(JSON.stringify({ project: proj2.projectId, run_id: runId2, link: reportLink(proj2.projectId, proj2.apiVersion), tools: rows, notes, headline: r.rendered.headline, verdicts, report: r.report }) + "\n");
|
|
@@ -7843,18 +8292,29 @@ function claudeOneShot2(spawn5, cfgPath, prompt, allowed) {
|
|
|
7843
8292
|
const res = spawn5("claude", claudeRecipe({ prompt, mcpConfigPath: cfgPath, allowedTools: allowed }), { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], timeout: 12e4, env: { ...process.env, CLAUDECODE: "" } });
|
|
7844
8293
|
return { calls: toolCallsInClaudeOutput(String(res.stdout ?? "")), status: res.status };
|
|
7845
8294
|
}
|
|
8295
|
+
function codexOneShot2(spawn5, o) {
|
|
8296
|
+
const env = { ...process.env };
|
|
8297
|
+
if (o.bearer) env.ABZ_BEARER = o.bearer;
|
|
8298
|
+
const res = spawn5("codex", codexRecipe({ prompt: o.prompt, serverUrl: o.serverUrl, runId: o.runId, enabledTools: o.enabledTools, bearerEnvVar: o.bearer ? "ABZ_BEARER" : null, cwd: o.cwd ?? import_os2.default.tmpdir() }), { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], timeout: 18e4, env });
|
|
8299
|
+
return { calls: toolCallsInCodexOutput(String(res.stdout ?? "")), status: res.status };
|
|
8300
|
+
}
|
|
7846
8301
|
async function createPromptTester(caller, proj2, opts = {}) {
|
|
7847
8302
|
const say = opts.say ?? ((s) => console.log(s));
|
|
7848
8303
|
const deps = opts.deps ?? {};
|
|
8304
|
+
const wanted = clientsFor(opts.platforms);
|
|
7849
8305
|
const claudeInstalled = deps.claudeInstalled ?? detectExternalClis().some((c) => c.kind === "claude");
|
|
7850
|
-
|
|
7851
|
-
|
|
8306
|
+
const codex = deps.codexInstalled !== void 0 ? { installed: deps.codexInstalled, loggedIn: deps.codexInstalled } : wanted.includes("codex") ? detectCodex(deps.spawn ?? import_child_process5.spawnSync) : { installed: false, loggedIn: false };
|
|
8307
|
+
const clients = wanted.filter((c) => c === "claude" ? claudeInstalled : codex.installed && codex.loggedIn);
|
|
8308
|
+
if (!clients.length) {
|
|
8309
|
+
say(import_chalk26.default.dim(` (${wanted.includes("codex") && !wanted.includes("claude") ? `Codex CLI is ${codex.installed ? "not logged in" : "not installed"}` : "Claude Code is not installed"} here: each prompt is run at the end, in the exercise)`));
|
|
7852
8310
|
return null;
|
|
7853
8311
|
}
|
|
8312
|
+
for (const missing of wanted.filter((c) => !clients.includes(c))) say(import_chalk26.default.dim(` (${missing === "claude" ? "Claude Code is not installed" : `Codex CLI is ${codex.installed ? "not logged in" : "not installed"}`} here: that pass runs at the end, in the exercise, once it is)`));
|
|
7854
8313
|
const r = await fetchDirectoryReport(caller, proj2, { probe: false });
|
|
7855
8314
|
const target2 = r.target ?? {};
|
|
7856
8315
|
const publishedUrl = r.report.project.publishedUrl;
|
|
7857
8316
|
if (!publishedUrl || target2.project?.directoryReady === false) return null;
|
|
8317
|
+
const readOnly2 = readOnlyToolNames(orderTools(target2.tools ?? []));
|
|
7858
8318
|
const open = target2.project?.authMode === "open";
|
|
7859
8319
|
let bearer = null;
|
|
7860
8320
|
let asMeta = null;
|
|
@@ -7879,16 +8339,22 @@ async function createPromptTester(caller, proj2, opts = {}) {
|
|
|
7879
8339
|
const configDir = import_fs5.default.mkdtempSync(import_path4.default.join(deps.tmpdir ?? import_os2.default.tmpdir(), "abz-prompt-"));
|
|
7880
8340
|
import_fs5.default.chmodSync(configDir, 448);
|
|
7881
8341
|
const cfgPath = import_path4.default.join(configDir, "mcp.json");
|
|
7882
|
-
import_fs5.default.writeFileSync(cfgPath, mcpConfigDocument(publishedUrl, bearer, runId2), { mode: 384 });
|
|
8342
|
+
if (clients.includes("claude")) import_fs5.default.writeFileSync(cfgPath, mcpConfigDocument(publishedUrl, bearer, runId2), { mode: 384 });
|
|
7883
8343
|
const notes = [];
|
|
7884
8344
|
return {
|
|
8345
|
+
clients,
|
|
7885
8346
|
async test(prompt) {
|
|
7886
|
-
const
|
|
7887
|
-
const
|
|
7888
|
-
|
|
7889
|
-
|
|
7890
|
-
|
|
7891
|
-
|
|
8347
|
+
const byClient = {};
|
|
8348
|
+
for (const client of clients) {
|
|
8349
|
+
const { calls } = client === "claude" ? claudeOneShot2(spawn5, cfgPath, prompt, readOnly2.map((n) => `mcp__abz__${n}`)) : codexOneShot2(spawn5, { prompt, serverUrl: publishedUrl, runId: runId2, enabledTools: readOnly2, bearer, cwd: deps.tmpdir });
|
|
8350
|
+
byClient[client] = { called: calls.length > 0, tools: calls };
|
|
8351
|
+
const note = { kind: "prompt", prompt, tool_called: calls.length > 0, tools: calls, client };
|
|
8352
|
+
const at = notes.findIndex((n) => n.prompt === prompt && n.client === client);
|
|
8353
|
+
if (at >= 0) notes[at] = note;
|
|
8354
|
+
else notes.push(note);
|
|
8355
|
+
}
|
|
8356
|
+
const all = Object.values(byClient);
|
|
8357
|
+
return { called: all.every((x) => x.called), tools: [...new Set(all.flatMap((x) => x.tools))], byClient };
|
|
7892
8358
|
},
|
|
7893
8359
|
notes: () => [...notes],
|
|
7894
8360
|
async close() {
|
|
@@ -7922,6 +8388,9 @@ async function runConnectorAnswers(project, opts = {}) {
|
|
|
7922
8388
|
const sponsored = parseYesNo("--sponsored-content", opts.sponsoredContent);
|
|
7923
8389
|
const access = opts.access ? opts.access.trim().toLowerCase() : void 0;
|
|
7924
8390
|
if (access && !["read", "write", "both"].includes(access)) throw new Error("--access must be read, write or both.");
|
|
8391
|
+
const platforms = opts.platforms?.length ? opts.platforms : ["claude", "chatgpt"];
|
|
8392
|
+
const forClaude = platforms.includes("claude");
|
|
8393
|
+
const forChatgpt = platforms.includes("chatgpt");
|
|
7925
8394
|
const answers = {};
|
|
7926
8395
|
for (const q of QUESTIONS) {
|
|
7927
8396
|
const v = opts[q.key];
|
|
@@ -7933,6 +8402,9 @@ async function runConnectorAnswers(project, opts = {}) {
|
|
|
7933
8402
|
if (dh) meta.data_handling = dh;
|
|
7934
8403
|
if (health) meta.health_data = health;
|
|
7935
8404
|
if (sponsored) meta.sponsored_content = sponsored;
|
|
8405
|
+
const { directoryBodyFromFlags: directoryBodyFromFlags2 } = await Promise.resolve().then(() => (init_directory(), directory_exports));
|
|
8406
|
+
Object.assign(meta, directoryBodyFromFlags2({ websiteUrl: opts.websiteUrl, supportUrl: opts.supportUrl, termsUrl: opts.termsUrl, developerName: opts.developerName, category: opts.chatgptCategory, demoRecordingUrl: opts.demoRecordingUrl, releaseNotes: opts.releaseNotes, shortDescription: opts.shortDescription, countries: opts.countries }).directory_metadata ?? {});
|
|
8407
|
+
if (opts.negativePrompt?.length) answers.chatgpt_test_cases = { positive: [], negative: opts.negativePrompt.slice(0, 3).map((p) => ({ prompt: p.trim(), description: "The plugin must not act on this; the assistant refuses or says it cannot." })) };
|
|
7936
8408
|
if (interactive2) {
|
|
7937
8409
|
const current = await producer(caller, { method: "POST", path: `${projectPath(proj2)}/directory-report`, body: { probe: false }, summary: "Current answers" });
|
|
7938
8410
|
const have = current.target?.metadata?.portalAnswers ?? {};
|
|
@@ -7961,18 +8433,19 @@ async function runConnectorAnswers(project, opts = {}) {
|
|
|
7961
8433
|
for (const q of QUESTIONS) {
|
|
7962
8434
|
if (answers[q.field] !== void 0) continue;
|
|
7963
8435
|
if (opts.derived === false && (q.field === "use_cases" || q.field === "reviewer_instructions")) continue;
|
|
8436
|
+
if (!forClaude && (q.field === "use_cases" || q.field === "reviewer_instructions" || q.field === "prerequisites" || q.field === "company_contact")) continue;
|
|
7964
8437
|
const existing = typeof have[q.field] === "string" ? String(have[q.field]) : "";
|
|
7965
8438
|
if (existing.length >= (q.min ?? 1)) continue;
|
|
7966
8439
|
const a = await ask3([{ type: "input", name: "value", message: q.message, default: existing || void 0, validate: (v) => v.trim().length >= (q.min ?? 1) ? true : `${q.min ?? 1}+ characters` }]);
|
|
7967
8440
|
answers[q.field] = a.value.trim();
|
|
7968
8441
|
await saveNow({ directory_portal_answers: { [q.field]: a.value.trim() } });
|
|
7969
8442
|
}
|
|
7970
|
-
if (answers.access === void 0 && !have.access) {
|
|
8443
|
+
if (forClaude && answers.access === void 0 && !have.access) {
|
|
7971
8444
|
const a = await ask3([{ type: "list", name: "value", message: "What the tools do:", choices: ["read", "write", "both"] }]);
|
|
7972
8445
|
answers.access = a.value;
|
|
7973
8446
|
await saveNow({ directory_portal_answers: { access: a.value } });
|
|
7974
8447
|
}
|
|
7975
|
-
if (answers.categories === void 0 && !(Array.isArray(have.categories) && have.categories.length)) {
|
|
8448
|
+
if (forClaude && answers.categories === void 0 && !(Array.isArray(have.categories) && have.categories.length)) {
|
|
7976
8449
|
const a = await ask3([{ type: "input", name: "value", message: "Category suggestions (1\u20135, comma-separated; you pick the closest in the portal):", validate: (v) => v.trim() ? true : "one at least" }]);
|
|
7977
8450
|
answers.categories = a.value.split(",").map((c) => c.trim()).filter(Boolean).slice(0, 5);
|
|
7978
8451
|
await saveNow({ directory_portal_answers: { categories: answers.categories } });
|
|
@@ -7985,16 +8458,113 @@ async function runConnectorAnswers(project, opts = {}) {
|
|
|
7985
8458
|
meta.data_handling = { mode: a.mode, ...a.statement?.trim() ? { statement: a.statement.trim() } : {} };
|
|
7986
8459
|
await saveNow({ directory_metadata: { data_handling: meta.data_handling } });
|
|
7987
8460
|
}
|
|
7988
|
-
if (!meta.health_data && !current.target?.metadata?.healthData) {
|
|
8461
|
+
if (forClaude && !meta.health_data && !current.target?.metadata?.healthData) {
|
|
7989
8462
|
const a = await ask3([{ type: "confirm", name: "value", message: "Do the tools handle health data?", default: false }]);
|
|
7990
8463
|
meta.health_data = a.value ? "yes" : "no";
|
|
7991
8464
|
await saveNow({ directory_metadata: { health_data: meta.health_data } });
|
|
7992
8465
|
}
|
|
7993
|
-
if (!meta.sponsored_content && !current.target?.metadata?.sponsoredContent) {
|
|
8466
|
+
if (forClaude && !meta.sponsored_content && !current.target?.metadata?.sponsoredContent) {
|
|
7994
8467
|
const a = await ask3([{ type: "confirm", name: "value", message: "Do tool results carry sponsored content?", default: false }]);
|
|
7995
8468
|
meta.sponsored_content = a.value ? "yes" : "no";
|
|
7996
8469
|
await saveNow({ directory_metadata: { sponsored_content: meta.sponsored_content } });
|
|
7997
8470
|
}
|
|
8471
|
+
if (forChatgpt) {
|
|
8472
|
+
const cm = current.target?.metadata ?? {};
|
|
8473
|
+
const { default: inquirer4 } = await import("inquirer");
|
|
8474
|
+
let headed2 = false;
|
|
8475
|
+
const ask22 = async (qs) => {
|
|
8476
|
+
if (!headed2) {
|
|
8477
|
+
headed2 = true;
|
|
8478
|
+
console.log(import_chalk26.default.bold(" What OpenAI's form asks (saved as you go):"));
|
|
8479
|
+
}
|
|
8480
|
+
return inquirer4.prompt(qs);
|
|
8481
|
+
};
|
|
8482
|
+
const apex = (() => {
|
|
8483
|
+
try {
|
|
8484
|
+
return new URL(String(cm.docsUrl ?? cm.privacyUrl ?? have.company_website ?? "")).origin;
|
|
8485
|
+
} catch {
|
|
8486
|
+
return "";
|
|
8487
|
+
}
|
|
8488
|
+
})();
|
|
8489
|
+
const urlQ = async (key, label3, def) => {
|
|
8490
|
+
if (cm[key === "website_url" ? "websiteUrl" : key === "support_url" ? "supportUrl" : "termsUrl"] || meta[key]) return;
|
|
8491
|
+
const a = await ask22([{ type: "input", name: "value", message: `${label3} (https; OpenAI requires it):`, ...def ? { default: def } : {}, validate: (v) => /^https:\/\/\S+$/i.test(v.trim()) ? true : "an https URL" }]);
|
|
8492
|
+
meta[key] = a.value.trim();
|
|
8493
|
+
await saveNow({ directory_metadata: { [key]: a.value.trim() } });
|
|
8494
|
+
};
|
|
8495
|
+
await urlQ("website_url", "Website URL", typeof have.company_website === "string" ? have.company_website : apex || void 0);
|
|
8496
|
+
await urlQ("support_url", "Support page URL", apex ? `${apex}/support` : void 0);
|
|
8497
|
+
await urlQ("terms_url", "Terms of service URL", apex ? `${apex}/terms` : void 0);
|
|
8498
|
+
if (!cm.developerName && !meta.developer_name) {
|
|
8499
|
+
const a = await ask22([{ type: "input", name: "value", message: "Developer name (\u2264 80; the directory shows your verified identity instead):", ...have.company_name ? { default: String(have.company_name) } : {}, validate: (v) => v.trim().length >= 1 && v.trim().length <= 80 ? true : "1\u201380 characters" }]);
|
|
8500
|
+
meta.developer_name = a.value.trim();
|
|
8501
|
+
await saveNow({ directory_metadata: { developer_name: meta.developer_name } });
|
|
8502
|
+
}
|
|
8503
|
+
if (!cm.category && !meta.category) {
|
|
8504
|
+
const a = await ask22([{ type: "list", name: "value", message: "Category (OpenAI's list):", choices: [...OPENAI_CATEGORIES] }]);
|
|
8505
|
+
meta.category = a.value;
|
|
8506
|
+
await saveNow({ directory_metadata: { category: a.value } });
|
|
8507
|
+
}
|
|
8508
|
+
if (!cm.shortDescription && !meta.short_description) {
|
|
8509
|
+
const tag = typeof cm.tagline === "string" ? cm.tagline : "";
|
|
8510
|
+
const a = await ask22([{ type: "input", name: "value", message: "ChatGPT subtitle (\u2264 30 characters; Claude keeps the long tagline):", ...tag ? { default: tag.slice(0, 30).replace(/\s+\S*$/, "") || tag.slice(0, 30) } : {}, validate: (v) => v.trim().length >= 1 && v.trim().length <= 30 ? true : `${v.trim().length}/30 characters` }]);
|
|
8511
|
+
meta.short_description = a.value.trim();
|
|
8512
|
+
await saveNow({ directory_metadata: { short_description: meta.short_description } });
|
|
8513
|
+
}
|
|
8514
|
+
if (!cm.demoRecordingUrl && !meta.demo_recording_url) {
|
|
8515
|
+
const a = await ask22([{ type: "input", name: "value", message: "Demo recording URL (OpenAI requires a walkthrough of the test cases on desktop and mobile; Enter to leave it for later):", validate: (v) => !v.trim() || /^https:\/\/\S+$/i.test(v.trim()) ? true : "an https URL, or Enter" }]);
|
|
8516
|
+
if (a.value.trim()) {
|
|
8517
|
+
meta.demo_recording_url = a.value.trim();
|
|
8518
|
+
await saveNow({ directory_metadata: { demo_recording_url: meta.demo_recording_url } });
|
|
8519
|
+
}
|
|
8520
|
+
}
|
|
8521
|
+
if (!cm.releaseNotes && !meta.release_notes) {
|
|
8522
|
+
const a = await ask22([{ type: "input", name: "value", message: "Release notes for this submission:", default: "Initial release." }]);
|
|
8523
|
+
meta.release_notes = a.value.trim() || "Initial release.";
|
|
8524
|
+
await saveNow({ directory_metadata: { release_notes: meta.release_notes } });
|
|
8525
|
+
}
|
|
8526
|
+
if (cm.countries == null && !meta.countries) {
|
|
8527
|
+
const a = await ask22([{ type: "input", name: "value", message: "Countries (ISO codes, comma-separated; Enter = everywhere):", validate: (v) => v.split(",").map((x) => x.trim()).filter(Boolean).every((x) => /^[A-Za-z]{2}$/.test(x)) ? true : "two-letter codes, like US, FR" }]);
|
|
8528
|
+
meta.countries = a.value.split(",").map((x) => x.trim().toUpperCase()).filter(Boolean);
|
|
8529
|
+
await saveNow({ directory_metadata: { countries: meta.countries } });
|
|
8530
|
+
}
|
|
8531
|
+
const cases = have.chatgpt_test_cases ?? null;
|
|
8532
|
+
if (answers.chatgpt_test_cases === void 0 && !((cases?.negative ?? []).length >= 3)) {
|
|
8533
|
+
const { defaultNegativePrompts: defaultNegativePrompts2 } = await Promise.resolve().then(() => (init_portal_drafts(), portal_drafts_exports));
|
|
8534
|
+
const open = current.target?.project?.authMode === "open";
|
|
8535
|
+
const tools = current.target?.tools ?? [];
|
|
8536
|
+
const defaults = defaultNegativePrompts2({ open, tools, serverName: typeof cm.serverName === "string" ? cm.serverName : null });
|
|
8537
|
+
console.log(import_chalk26.default.dim(" Three prompts the plugin must NOT act on (OpenAI asks for them; the exercise checks that no tool is called):"));
|
|
8538
|
+
const negative = [];
|
|
8539
|
+
for (let i = 0; i < 3; i++) {
|
|
8540
|
+
const a = await ask22([{ type: "input", name: "value", message: `Negative prompt ${i + 1}:`, default: defaults[i].prompt }]);
|
|
8541
|
+
const prompt = a.value.trim() || defaults[i].prompt;
|
|
8542
|
+
negative.push({ prompt, description: prompt === defaults[i].prompt ? defaults[i].description : "The plugin must not act on this; the assistant refuses or says it cannot." });
|
|
8543
|
+
}
|
|
8544
|
+
answers.chatgpt_test_cases = { positive: Array.isArray(cases?.positive) ? cases.positive : [], negative };
|
|
8545
|
+
await saveNow({ directory_portal_answers: { chatgpt_test_cases: answers.chatgpt_test_cases } });
|
|
8546
|
+
}
|
|
8547
|
+
const prompts = Array.isArray(cm.examplePrompts) ? cm.examplePrompts.filter((p) => typeof p === "string" && p.trim()) : [];
|
|
8548
|
+
if (prompts.length >= 3 && prompts.length < 5) {
|
|
8549
|
+
console.log(import_chalk26.default.dim(` OpenAI wants five positive cases; you have ${prompts.length} prompts. ${5 - prompts.length} more a reviewer can try:`));
|
|
8550
|
+
const extra = [...prompts];
|
|
8551
|
+
for (let i = prompts.length; i < 5; i++) {
|
|
8552
|
+
const a = await ask22([{ type: "input", name: "value", message: `Prompt ${i + 1} (Enter to stop here):`, validate: (v) => v.trim().length <= 128 ? true : `${v.trim().length} characters; ChatGPT allows 128` }]);
|
|
8553
|
+
const p = a.value.trim();
|
|
8554
|
+
if (!p) break;
|
|
8555
|
+
if (opts.testPrompt) {
|
|
8556
|
+
console.log(import_chalk26.default.dim(" trying it\u2026"));
|
|
8557
|
+
const res = await opts.testPrompt(p);
|
|
8558
|
+
console.log(res.called ? import_chalk26.default.green(` \u2713 called ${res.tools.join(", ")}`) : import_chalk26.default.yellow(" \u2013 no tool call; kept (the exercise will say so)"));
|
|
8559
|
+
}
|
|
8560
|
+
extra.push(p);
|
|
8561
|
+
}
|
|
8562
|
+
if (extra.length > prompts.length) {
|
|
8563
|
+
meta.example_prompts = extra;
|
|
8564
|
+
await saveNow({ directory_metadata: { example_prompts: extra } });
|
|
8565
|
+
}
|
|
8566
|
+
}
|
|
8567
|
+
}
|
|
7998
8568
|
}
|
|
7999
8569
|
const body = {};
|
|
8000
8570
|
if (Object.keys(answers).length) body.directory_portal_answers = answers;
|
|
@@ -8027,6 +8597,7 @@ var init_connector_exercise = __esm({
|
|
|
8027
8597
|
init_directory();
|
|
8028
8598
|
init_headless_login();
|
|
8029
8599
|
init_exercise();
|
|
8600
|
+
init_directory_probes();
|
|
8030
8601
|
init_brand();
|
|
8031
8602
|
init_portal_drafts();
|
|
8032
8603
|
init_external_mcp();
|
|
@@ -8231,7 +8802,7 @@ async function askDocsQuestion(caller, proj2, state, interactive2, say, deps = {
|
|
|
8231
8802
|
if (!v || /^n(o)?$/i.test(v)) {
|
|
8232
8803
|
say(import_chalk27.default.dim(" using the generated page"));
|
|
8233
8804
|
try {
|
|
8234
|
-
await runConnectorDocs(proj2.
|
|
8805
|
+
await runConnectorDocs(proj2.projectId, { deps, yes: true, team: proj2.teamId });
|
|
8235
8806
|
} catch (e) {
|
|
8236
8807
|
say(import_chalk27.default.yellow(` ${e instanceof Error ? e.message : String(e)}`));
|
|
8237
8808
|
await producer(caller, { method: "PUT", path: `${projectPath2(proj2)}/directory-documents`, body: { use_generated_docs_url: true }, summary: "Point docs at the generated page" });
|
|
@@ -8264,6 +8835,65 @@ var init_connector_docs = __esm({
|
|
|
8264
8835
|
}
|
|
8265
8836
|
});
|
|
8266
8837
|
|
|
8838
|
+
// src/lib/plugin-package.ts
|
|
8839
|
+
var plugin_package_exports = {};
|
|
8840
|
+
__export(plugin_package_exports, {
|
|
8841
|
+
writePluginPackage: () => writePluginPackage
|
|
8842
|
+
});
|
|
8843
|
+
async function writePluginPackage(dir, m, deps = {}) {
|
|
8844
|
+
const f2 = deps.fetch ?? fetch;
|
|
8845
|
+
const dirPath = import_path6.default.join(dir, m.package_name);
|
|
8846
|
+
import_fs7.default.mkdirSync(import_path6.default.join(dirPath, "assets"), { recursive: true });
|
|
8847
|
+
const problems = [];
|
|
8848
|
+
const plugin = JSON.parse(JSON.stringify(m.plugin));
|
|
8849
|
+
for (const k of FORBIDDEN) {
|
|
8850
|
+
if (k in plugin) delete plugin[k];
|
|
8851
|
+
}
|
|
8852
|
+
let logoFile = "assets/logo.png";
|
|
8853
|
+
if (m.logo_url) {
|
|
8854
|
+
try {
|
|
8855
|
+
const res = await f2(m.logo_url, { signal: AbortSignal.timeout(1e4) });
|
|
8856
|
+
const buf = new Uint8Array(await res.arrayBuffer());
|
|
8857
|
+
if (!res.ok) problems.push(`the icon at ${m.logo_url} answered ${res.status}`);
|
|
8858
|
+
else {
|
|
8859
|
+
const why = logoProblem(buf);
|
|
8860
|
+
if (why) problems.push(why);
|
|
8861
|
+
logoFile = `assets/logo.${LOGO_EXT[imageInfo(buf).format]}`;
|
|
8862
|
+
import_fs7.default.writeFileSync(import_path6.default.join(dirPath, logoFile), buf, { mode: 420 });
|
|
8863
|
+
}
|
|
8864
|
+
} catch (e) {
|
|
8865
|
+
problems.push(`the icon could not be downloaded: ${e instanceof Error ? e.message : String(e)}`);
|
|
8866
|
+
}
|
|
8867
|
+
} else problems.push("no icon URL: OpenAI requires a logo (square, \u2265 48 px)");
|
|
8868
|
+
if (m.icon_source === "default") problems.push("the icon is APIblaze's default logo; OpenAI lists only IP you own");
|
|
8869
|
+
const iface = plugin.extensions?.["com.openai"]?.interface;
|
|
8870
|
+
if (iface) iface.logo = `./${logoFile}`;
|
|
8871
|
+
const pluginText = JSON.stringify(plugin, null, 2) + "\n";
|
|
8872
|
+
const mcpText = JSON.stringify(m.mcp, null, 2) + "\n";
|
|
8873
|
+
if (/"headers"\s*:/.test(mcpText)) problems.push("mcp.json carries server headers; OpenAI rejects a public submission with them");
|
|
8874
|
+
import_fs7.default.writeFileSync(import_path6.default.join(dirPath, "plugin.json"), pluginText, { mode: 420 });
|
|
8875
|
+
import_fs7.default.writeFileSync(import_path6.default.join(dirPath, "mcp.json"), mcpText, { mode: 420 });
|
|
8876
|
+
const entries2 = [
|
|
8877
|
+
{ path: "plugin.json", data: new TextEncoder().encode(pluginText) },
|
|
8878
|
+
{ path: "mcp.json", data: new TextEncoder().encode(mcpText) }
|
|
8879
|
+
];
|
|
8880
|
+
const logoPath = import_path6.default.join(dirPath, logoFile);
|
|
8881
|
+
if (import_fs7.default.existsSync(logoPath)) entries2.push({ path: logoFile, data: new Uint8Array(import_fs7.default.readFileSync(logoPath)) });
|
|
8882
|
+
const zipPath = import_path6.default.join(dir, `${m.package_name}.zip`);
|
|
8883
|
+
import_fs7.default.writeFileSync(zipPath, zipStore(entries2), { mode: 420 });
|
|
8884
|
+
return { dirPath, zipPath, seq: m.seq, problem: problems.length ? `OpenAI package: ${problems.join("; ")}.` : null };
|
|
8885
|
+
}
|
|
8886
|
+
var import_fs7, import_path6, FORBIDDEN;
|
|
8887
|
+
var init_plugin_package = __esm({
|
|
8888
|
+
"src/lib/plugin-package.ts"() {
|
|
8889
|
+
"use strict";
|
|
8890
|
+
import_fs7 = __toESM(require("fs"));
|
|
8891
|
+
import_path6 = __toESM(require("path"));
|
|
8892
|
+
init_zip();
|
|
8893
|
+
FORBIDDEN = ["test_credentials", "reviewer_instructions", "clientSecret", "client_secret", "screenshots", "apps", "hooks"];
|
|
8894
|
+
}
|
|
8895
|
+
});
|
|
8896
|
+
|
|
8267
8897
|
// src/lib/agent-chat.ts
|
|
8268
8898
|
async function runAgentChatRepl(opts) {
|
|
8269
8899
|
const { title, subtitle, endpoint, buildBody, seedPrompt, summarizeProposal, commands } = opts;
|
|
@@ -8501,7 +9131,7 @@ function requireTool(cat, tool) {
|
|
|
8501
9131
|
function withOverride(cat, tool, flags) {
|
|
8502
9132
|
requireTool(cat, tool);
|
|
8503
9133
|
const patch = {};
|
|
8504
|
-
for (const k of ["readOnly", "destructive", "idempotent", "confirm", "alwaysAsync"]) {
|
|
9134
|
+
for (const k of ["readOnly", "destructive", "idempotent", "confirm", "alwaysAsync", "openWorld"]) {
|
|
8505
9135
|
if (typeof flags[k] === "boolean") patch[k] = flags[k];
|
|
8506
9136
|
}
|
|
8507
9137
|
if (typeof flags.title === "string") {
|
|
@@ -8510,7 +9140,7 @@ function withOverride(cat, tool, flags) {
|
|
|
8510
9140
|
patch.title = t;
|
|
8511
9141
|
}
|
|
8512
9142
|
if (Object.keys(patch).length === 0) {
|
|
8513
|
-
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).");
|
|
8514
9144
|
}
|
|
8515
9145
|
return { ...cat, overrides: { ...cat.overrides ?? {}, [tool]: { ...cat.overrides?.[tool] ?? {}, ...patch } } };
|
|
8516
9146
|
}
|
|
@@ -8604,7 +9234,7 @@ async function runMcpPrompts(action2, project, name, opts) {
|
|
|
8604
9234
|
if (!opts.file) throw new Error(`Usage: ${B.cli} mcp prompts set <project> <name> --file prompt.json`);
|
|
8605
9235
|
let raw;
|
|
8606
9236
|
try {
|
|
8607
|
-
raw = JSON.parse(
|
|
9237
|
+
raw = JSON.parse(import_fs8.default.readFileSync(opts.file, "utf8"));
|
|
8608
9238
|
} catch (e) {
|
|
8609
9239
|
throw new Error(`Could not read ${opts.file}: ${e instanceof Error ? e.message : String(e)}`);
|
|
8610
9240
|
}
|
|
@@ -8663,11 +9293,11 @@ async function runMcpUi(project, tool, opts) {
|
|
|
8663
9293
|
console.log(import_chalk29.default.dim(" This is the automatic viewer: MCP Apps clients render the tool's result with a generic UI built"));
|
|
8664
9294
|
console.log(import_chalk29.default.dim(" from its response schema \u2014 nothing to configure. Uploading your own HTML for it comes later.\n"));
|
|
8665
9295
|
}
|
|
8666
|
-
var
|
|
9296
|
+
var import_fs8, import_chalk29, PROMPT_NAME_RE;
|
|
8667
9297
|
var init_mcp = __esm({
|
|
8668
9298
|
"src/commands/mcp.ts"() {
|
|
8669
9299
|
"use strict";
|
|
8670
|
-
|
|
9300
|
+
import_fs8 = __toESM(require("fs"));
|
|
8671
9301
|
import_chalk29 = __toESM(require("chalk"));
|
|
8672
9302
|
init_brand();
|
|
8673
9303
|
init_auth();
|
|
@@ -8695,7 +9325,7 @@ function createPlaneCore(cfg) {
|
|
|
8695
9325
|
const iamBase = (cfg.iamBase ?? "https://iam.apiblaze.com").replace(/\/$/, "");
|
|
8696
9326
|
const tenantCache = /* @__PURE__ */ new Map();
|
|
8697
9327
|
const endUserCache = /* @__PURE__ */ new Map();
|
|
8698
|
-
function planeFetch(base2, method,
|
|
9328
|
+
function planeFetch(base2, method, path20, user, opts = {}) {
|
|
8699
9329
|
const params = new URLSearchParams({ product: "apiblaze", ...opts.query ?? {} });
|
|
8700
9330
|
const headers = { "X-API-Key": cfg.cpKey, "X-End-User-Id": user.userId };
|
|
8701
9331
|
let body;
|
|
@@ -8703,7 +9333,7 @@ function createPlaneCore(cfg) {
|
|
|
8703
9333
|
headers["Content-Type"] = "application/json";
|
|
8704
9334
|
body = JSON.stringify(opts.body);
|
|
8705
9335
|
}
|
|
8706
|
-
return fetch(`${base2}${
|
|
9336
|
+
return fetch(`${base2}${path20}?${params.toString()}`, { method, headers, body, cache: "no-store" });
|
|
8707
9337
|
}
|
|
8708
9338
|
async function resolveTenant2(user) {
|
|
8709
9339
|
const cached = tenantCache.get(user.tenant);
|
|
@@ -10128,15 +10758,15 @@ async function freePort(from, avoid = []) {
|
|
|
10128
10758
|
}
|
|
10129
10759
|
function readRegistry() {
|
|
10130
10760
|
try {
|
|
10131
|
-
return JSON.parse(
|
|
10761
|
+
return JSON.parse(import_fs11.default.readFileSync(registryFile(), "utf8"));
|
|
10132
10762
|
} catch {
|
|
10133
10763
|
return [];
|
|
10134
10764
|
}
|
|
10135
10765
|
}
|
|
10136
10766
|
function writeRegistry(rows) {
|
|
10137
10767
|
try {
|
|
10138
|
-
|
|
10139
|
-
|
|
10768
|
+
import_fs11.default.mkdirSync(import_path9.default.dirname(registryFile()), { recursive: true });
|
|
10769
|
+
import_fs11.default.writeFileSync(registryFile(), JSON.stringify(rows));
|
|
10140
10770
|
} catch {
|
|
10141
10771
|
}
|
|
10142
10772
|
}
|
|
@@ -10180,8 +10810,8 @@ async function stopTunnels() {
|
|
|
10180
10810
|
return stopped;
|
|
10181
10811
|
}
|
|
10182
10812
|
function appCtl(dir, arg) {
|
|
10183
|
-
if (!
|
|
10184
|
-
const r = (0, import_child_process7.spawnSync)(process.execPath, [
|
|
10813
|
+
if (!import_fs11.default.existsSync(import_path9.default.join(dir, "start.js"))) return false;
|
|
10814
|
+
const r = (0, import_child_process7.spawnSync)(process.execPath, [import_path9.default.join(dir, "start.js"), arg], { cwd: dir, stdio: "ignore", timeout: 15e3 });
|
|
10185
10815
|
return r.status === 0;
|
|
10186
10816
|
}
|
|
10187
10817
|
function isDemoProcess(args) {
|
|
@@ -10221,47 +10851,47 @@ async function stopDemos() {
|
|
|
10221
10851
|
return n + left.length;
|
|
10222
10852
|
}
|
|
10223
10853
|
function writeApp(dir) {
|
|
10224
|
-
|
|
10225
|
-
for (const entry of
|
|
10854
|
+
import_fs11.default.mkdirSync(dir, { recursive: true });
|
|
10855
|
+
for (const entry of import_fs11.default.readdirSync(dir)) import_fs11.default.rmSync(import_path9.default.join(dir, entry), { recursive: true, force: true });
|
|
10226
10856
|
for (const [rel, text] of Object.entries(DEMO_FILES)) {
|
|
10227
|
-
const f2 =
|
|
10228
|
-
|
|
10229
|
-
|
|
10857
|
+
const f2 = import_path9.default.join(dir, rel);
|
|
10858
|
+
import_fs11.default.mkdirSync(import_path9.default.dirname(f2), { recursive: true });
|
|
10859
|
+
import_fs11.default.writeFileSync(f2, text);
|
|
10230
10860
|
}
|
|
10231
|
-
|
|
10861
|
+
import_fs11.default.writeFileSync(import_path9.default.join(dir, "package.json"), JSON.stringify({
|
|
10232
10862
|
name: "nino-pizza-demo",
|
|
10233
10863
|
private: true,
|
|
10234
10864
|
scripts: { start: "node start.js --background", stop: "node start.js --stop", restart: "node start.js --restart", dev: "node start.js" }
|
|
10235
10865
|
}, null, 2) + "\n");
|
|
10236
|
-
|
|
10237
|
-
for (const [from, to] of [[
|
|
10238
|
-
if (!
|
|
10239
|
-
|
|
10240
|
-
|
|
10866
|
+
import_fs11.default.writeFileSync(import_path9.default.join(dir, ".gitignore"), ".env.local\n.demo/\n");
|
|
10867
|
+
for (const [from, to] of [[import_path9.default.join(__dirname, "server", "index.js"), import_path9.default.join(dir, "lib", "apiblaze-server.js")], [import_path9.default.join(__dirname, "browser", "widgets.js"), import_path9.default.join(dir, "public", "apiblaze-widgets.js")]]) {
|
|
10868
|
+
if (!import_fs11.default.existsSync(from)) continue;
|
|
10869
|
+
import_fs11.default.mkdirSync(import_path9.default.dirname(to), { recursive: true });
|
|
10870
|
+
import_fs11.default.copyFileSync(from, to);
|
|
10241
10871
|
}
|
|
10242
10872
|
}
|
|
10243
10873
|
function saveState(dir, s) {
|
|
10244
|
-
const d =
|
|
10245
|
-
|
|
10246
|
-
|
|
10874
|
+
const d = import_path9.default.join(dir, STATE_DIR);
|
|
10875
|
+
import_fs11.default.mkdirSync(d, { recursive: true });
|
|
10876
|
+
import_fs11.default.writeFileSync(import_path9.default.join(d, "state.json"), JSON.stringify(s, null, 2), { mode: 384 });
|
|
10247
10877
|
}
|
|
10248
10878
|
function loadState(dir) {
|
|
10249
10879
|
try {
|
|
10250
|
-
return JSON.parse(
|
|
10880
|
+
return JSON.parse(import_fs11.default.readFileSync(import_path9.default.join(dir, STATE_DIR, "state.json"), "utf8"));
|
|
10251
10881
|
} catch {
|
|
10252
10882
|
return null;
|
|
10253
10883
|
}
|
|
10254
10884
|
}
|
|
10255
10885
|
function writeEnv(dir, v) {
|
|
10256
10886
|
const lines = Object.entries(v).map(([k, val]) => `${k}=${val}`);
|
|
10257
|
-
|
|
10887
|
+
import_fs11.default.writeFileSync(import_path9.default.join(dir, ".env.local"), `# Written by ${LATEST} demo. Server-side only: never commit or share.
|
|
10258
10888
|
${lines.join("\n")}
|
|
10259
10889
|
`, { mode: 384 });
|
|
10260
10890
|
}
|
|
10261
10891
|
function readEnvFile(file2) {
|
|
10262
10892
|
const out = {};
|
|
10263
10893
|
try {
|
|
10264
|
-
for (const line of
|
|
10894
|
+
for (const line of import_fs11.default.readFileSync(file2, "utf8").split("\n")) {
|
|
10265
10895
|
const m = /^([A-Z0-9_]+)=(.*)$/.exec(line.trim());
|
|
10266
10896
|
if (m) out[m[1]] = m[2];
|
|
10267
10897
|
}
|
|
@@ -10271,14 +10901,14 @@ function readEnvFile(file2) {
|
|
|
10271
10901
|
}
|
|
10272
10902
|
function demoDir() {
|
|
10273
10903
|
const here = process.cwd();
|
|
10274
|
-
if (
|
|
10275
|
-
return
|
|
10904
|
+
if (import_fs11.default.existsSync(import_path9.default.join(here, STATE_DIR, "state.json"))) return here;
|
|
10905
|
+
return import_path9.default.resolve(here, DIR_NAME);
|
|
10276
10906
|
}
|
|
10277
10907
|
function adoptWorkspace(cpKey, teamId, claimCode) {
|
|
10278
10908
|
const prev = loadAnonCred();
|
|
10279
10909
|
if (prev?.cp_key && prev.team_id && prev.team_id !== teamId) {
|
|
10280
10910
|
try {
|
|
10281
|
-
|
|
10911
|
+
import_fs11.default.writeFileSync(import_path9.default.join(getApiblazeDir(), `anon.${prev.team_id}.json`), JSON.stringify(prev, null, 2), { mode: 384 });
|
|
10282
10912
|
} catch {
|
|
10283
10913
|
}
|
|
10284
10914
|
}
|
|
@@ -10370,7 +11000,7 @@ function openBrowser3(url) {
|
|
|
10370
11000
|
}
|
|
10371
11001
|
async function offerBrowserAndLogs(dir, opts) {
|
|
10372
11002
|
if (opts.open === false || opts.yes || !interactive()) return;
|
|
10373
|
-
const env = readEnvFile(
|
|
11003
|
+
const env = readEnvFile(import_path9.default.join(dir, ".env.local"));
|
|
10374
11004
|
const url = `http://localhost:${env.APP_PORT}`;
|
|
10375
11005
|
console.log();
|
|
10376
11006
|
if (await ask(` Open ${url} in your browser? (Y/n) `)) openBrowser3(url);
|
|
@@ -10405,7 +11035,7 @@ function routesOf(openapi) {
|
|
|
10405
11035
|
return out;
|
|
10406
11036
|
}
|
|
10407
11037
|
async function runDemo(opts = {}) {
|
|
10408
|
-
if (opts.serve) return serveTunnel(
|
|
11038
|
+
if (opts.serve) return serveTunnel(import_path9.default.resolve(opts.serve));
|
|
10409
11039
|
try {
|
|
10410
11040
|
countRun({ event: await startDemo(opts) });
|
|
10411
11041
|
} catch (err) {
|
|
@@ -10461,8 +11091,8 @@ function printTable(rows) {
|
|
|
10461
11091
|
async function startDemo(opts) {
|
|
10462
11092
|
const t0 = Date.now();
|
|
10463
11093
|
const here = process.cwd();
|
|
10464
|
-
const dir =
|
|
10465
|
-
const rel =
|
|
11094
|
+
const dir = import_fs11.default.existsSync(import_path9.default.join(here, STATE_DIR, "state.json")) ? here : import_path9.default.resolve(here, DIR_NAME);
|
|
11095
|
+
const rel = import_path9.default.relative(here, dir) || ".";
|
|
10466
11096
|
const cdDir = rel === "." ? "" : `cd ${rel} && `;
|
|
10467
11097
|
const shell = (c, note = "") => console.log(` ${import_chalk33.default.dim("$")} ${import_chalk33.default.cyan(c)}${note ? ` ${import_chalk33.default.dim(note)}` : ""}`);
|
|
10468
11098
|
const ok = (text, note = "") => console.log(` ${import_chalk33.default.green("\u2713")} ${text}${note ? ` ${import_chalk33.default.dim(note)}` : ""}`);
|
|
@@ -10482,8 +11112,8 @@ async function startDemo(opts) {
|
|
|
10482
11112
|
const backendSecret = import_crypto.default.randomBytes(18).toString("base64url");
|
|
10483
11113
|
const openapi = DEMO_FILES["openapi.yaml"];
|
|
10484
11114
|
try {
|
|
10485
|
-
const specPath =
|
|
10486
|
-
|
|
11115
|
+
const specPath = import_path9.default.join(dir, "openapi.yaml");
|
|
11116
|
+
import_fs11.default.writeFileSync(specPath, import_fs11.default.readFileSync(specPath, "utf8").replace(/^paths:/m, `servers:
|
|
10487
11117
|
- url: http://localhost:${backendPort2}
|
|
10488
11118
|
paths:`));
|
|
10489
11119
|
} catch {
|
|
@@ -10505,12 +11135,12 @@ paths:`));
|
|
|
10505
11135
|
if (!appCtl(dir, "--background")) throw new DemoError("The ResiResi app could not start.", `Run again: ${LATEST} demo`, "app-start");
|
|
10506
11136
|
registerDemo(dir);
|
|
10507
11137
|
const appUp = await busy("Starting the reservation backend and its front-end\u2026", () => waitPorts([backendPort2, directPort]), 1800);
|
|
10508
|
-
if (!appUp) throw new DemoError(`The ResiResi app did not start (see ${
|
|
11138
|
+
if (!appUp) throw new DemoError(`The ResiResi app did not start (see ${import_path9.default.join(rel, STATE_DIR, "app.log")}).`, `Run again: ${LATEST} demo`, "app-not-up");
|
|
10509
11139
|
ok(`${import_chalk33.default.bold("Reservation backend")}: localhost:${backendPort2}`, "unprotected");
|
|
10510
11140
|
ok(`${import_chalk33.default.bold("Unprotected reservation front-end")}, going directly to the reservation backend: ${import_chalk33.default.bold(`http://localhost:${directPort}`)}`);
|
|
10511
11141
|
console.log(" Book a table directly, the way the unprotected front-end does:");
|
|
10512
11142
|
console.log(import_chalk33.default.cyan(` curl -X POST http://localhost:${backendPort2}/reservations \\`));
|
|
10513
|
-
console.log(import_chalk33.default.cyan(` -H "x-target-api-key: $(grep BACKEND_SECRET ${
|
|
11143
|
+
console.log(import_chalk33.default.cyan(` -H "x-target-api-key: $(grep BACKEND_SECRET ${import_path9.default.join(rel, ".env.local")} | cut -d= -f2)" \\`));
|
|
10514
11144
|
console.log(import_chalk33.default.cyan(` -H "x-abz-user-id: ana" -H "x-abz-tenant-id: ${ninoTenant}" \\`));
|
|
10515
11145
|
const tomorrow = new Date(Date.now() + 864e5);
|
|
10516
11146
|
const day = `${tomorrow.getFullYear()}-${String(tomorrow.getMonth() + 1).padStart(2, "0")}-${String(tomorrow.getDate()).padStart(2, "0")}`;
|
|
@@ -10650,23 +11280,23 @@ paths:`));
|
|
|
10650
11280
|
appCtl(dir, "--background");
|
|
10651
11281
|
return waitPorts([appPort, directPort, backendPort2]);
|
|
10652
11282
|
}, 1500);
|
|
10653
|
-
if (!siteUp) throw new DemoError(`The protected website did not start (see ${
|
|
11283
|
+
if (!siteUp) throw new DemoError(`The protected website did not start (see ${import_path9.default.join(rel, STATE_DIR, "app.log")}).`, `Try: ${cdDir}npm run restart`, "site-not-up");
|
|
10654
11284
|
ok(`${import_chalk33.default.bold("Protected reservation website")}, going through the ${B.product} proxy ${apiUrl}: ${import_chalk33.default.bold(`http://localhost:${appPort}`)}`);
|
|
10655
11285
|
ok(`${import_chalk33.default.bold("Unprotected reservation front-end")}, still going directly to the backend: http://localhost:${directPort}`, "the switch at the top of the page goes between them");
|
|
10656
11286
|
console.log();
|
|
10657
|
-
console.log(import_chalk33.default.dim(` Logs: ${logsCommand(readEnvFile(
|
|
11287
|
+
console.log(import_chalk33.default.dim(` Logs: ${logsCommand(readEnvFile(import_path9.default.join(dir, ".env.local")))} \xB7 Stop the tunnel: ${LATEST} demo stop \xB7 Your own app: ${LATEST} dev --port <port>`));
|
|
10658
11288
|
await offerBrowserAndLogs(dir, opts);
|
|
10659
11289
|
return "demo-ready";
|
|
10660
11290
|
}
|
|
10661
11291
|
function printAddresses(dir) {
|
|
10662
|
-
const env = readEnvFile(
|
|
11292
|
+
const env = readEnvFile(import_path9.default.join(dir, ".env.local"));
|
|
10663
11293
|
const row = (label3, value, note = "") => console.log(` ${label3.padEnd(11)} ${value}${note ? ` ${import_chalk33.default.dim(note)}` : ""}`);
|
|
10664
11294
|
row("Public API", env.APIBLAZE_URL ?? "", `${B.product} tunnel \u2192 localhost:${env.BACKEND_PORT}`);
|
|
10665
11295
|
row("MCP", env.APIBLAZE_MCP_URL ?? "", "for Claude, ChatGPT, Cursor (Nino Pizza)");
|
|
10666
11296
|
if (env.APIBLAZE_PORTAL_URL) row("Dev portal", env.APIBLAZE_PORTAL_URL, "sign in, get a key, try the API");
|
|
10667
11297
|
}
|
|
10668
11298
|
async function spawnTunnel(dir, state) {
|
|
10669
|
-
const log4 =
|
|
11299
|
+
const log4 = import_fs11.default.openSync(import_path9.default.join(dir, STATE_DIR, "tunnel.log"), "a");
|
|
10670
11300
|
const child = (0, import_child_process7.spawn)(process.execPath, [process.argv[1], "demo", "--serve", dir], { cwd: dir, detached: true, stdio: ["ignore", log4, log4] });
|
|
10671
11301
|
child.unref();
|
|
10672
11302
|
saveState(dir, { ...state, pid: child.pid, ready: false, error: void 0 });
|
|
@@ -10677,7 +11307,7 @@ async function runDemoStart(opts = {}) {
|
|
|
10677
11307
|
const t0 = Date.now();
|
|
10678
11308
|
const dir = demoDir();
|
|
10679
11309
|
const state = loadState(dir);
|
|
10680
|
-
const env = readEnvFile(
|
|
11310
|
+
const env = readEnvFile(import_path9.default.join(dir, ".env.local"));
|
|
10681
11311
|
if (!state || !env.APIBLAZE_SERVER_KEY) {
|
|
10682
11312
|
console.log(`${import_chalk33.default.red("\u2717")} There is no demo here to start.`);
|
|
10683
11313
|
console.log(` Make one: ${LATEST} demo`);
|
|
@@ -10697,11 +11327,11 @@ async function runDemoStart(opts = {}) {
|
|
|
10697
11327
|
console.log(`${import_chalk33.default.green("\u2713")} Tunnel open: the public address reaches localhost:${env.BACKEND_PORT} again ${import_chalk33.default.dim(`(${Math.round((Date.now() - t0) / 1e3)} s)`)}`);
|
|
10698
11328
|
printAddresses(dir);
|
|
10699
11329
|
const appUp = await fetch(`http://127.0.0.1:${env.APP_PORT}/api/config`).then(() => true).catch(() => false);
|
|
10700
|
-
if (!appUp) console.log(import_chalk33.default.yellow(` ResiResi is not running: cd ${
|
|
11330
|
+
if (!appUp) console.log(import_chalk33.default.yellow(` ResiResi is not running: cd ${import_path9.default.relative(process.cwd(), dir) || "."} && npm run start`));
|
|
10701
11331
|
await offerBrowserAndLogs(dir, opts);
|
|
10702
11332
|
}
|
|
10703
11333
|
async function runDemoLogs() {
|
|
10704
|
-
const env = readEnvFile(
|
|
11334
|
+
const env = readEnvFile(import_path9.default.join(demoDir(), ".env.local"));
|
|
10705
11335
|
if (!env.APIBLAZE_NAME) {
|
|
10706
11336
|
console.log(`${import_chalk33.default.red("\u2717")} There is no demo here.`);
|
|
10707
11337
|
console.log(` Make one: ${LATEST} demo`);
|
|
@@ -10771,12 +11401,12 @@ async function serveTunnel(dir) {
|
|
|
10771
11401
|
await new Promise(() => {
|
|
10772
11402
|
});
|
|
10773
11403
|
}
|
|
10774
|
-
var
|
|
11404
|
+
var import_fs11, import_path9, import_net2, import_child_process7, import_crypto, import_chalk33, import_ora11, DIR_NAME, STATE_DIR, LATEST, DEMO_RPS, DEMO_ADMIN_EMAIL, DEMO_GINO_ADMIN_EMAIL, RULE_SENTENCE, DemoError, ADJ, NOUN, pick2, registryFile, alive, interactive, logsCommand;
|
|
10775
11405
|
var init_demo = __esm({
|
|
10776
11406
|
"src/commands/demo.ts"() {
|
|
10777
11407
|
"use strict";
|
|
10778
|
-
|
|
10779
|
-
|
|
11408
|
+
import_fs11 = __toESM(require("fs"));
|
|
11409
|
+
import_path9 = __toESM(require("path"));
|
|
10780
11410
|
import_net2 = __toESM(require("net"));
|
|
10781
11411
|
import_child_process7 = require("child_process");
|
|
10782
11412
|
import_crypto = __toESM(require("crypto"));
|
|
@@ -10807,7 +11437,7 @@ var init_demo = __esm({
|
|
|
10807
11437
|
ADJ = ["brave", "calm", "clever", "eager", "happy", "jolly", "lucky", "merry", "noble", "quick", "sunny", "swift", "witty", "zesty", "bright", "gentle"];
|
|
10808
11438
|
NOUN = ["otter", "falcon", "panda", "heron", "koala", "lemur", "puffin", "robin", "tiger", "walrus", "wombat", "badger", "comet", "maple", "river", "finch"];
|
|
10809
11439
|
pick2 = (a) => a[import_crypto.default.randomInt(a.length)];
|
|
10810
|
-
registryFile = () =>
|
|
11440
|
+
registryFile = () => import_path9.default.join(getApiblazeDir(), "demo-runs.json");
|
|
10811
11441
|
alive = (pid) => {
|
|
10812
11442
|
try {
|
|
10813
11443
|
process.kill(pid, 0);
|
|
@@ -11072,7 +11702,7 @@ function runCli(args, cwd, home, timeoutMs) {
|
|
|
11072
11702
|
function readEnv(file2) {
|
|
11073
11703
|
const out = {};
|
|
11074
11704
|
try {
|
|
11075
|
-
for (const line of
|
|
11705
|
+
for (const line of import_fs14.default.readFileSync(file2, "utf8").split("\n")) {
|
|
11076
11706
|
const m = /^([A-Z0-9_]+)=(.*)$/.exec(line.trim());
|
|
11077
11707
|
if (m) out[m[1]] = m[2];
|
|
11078
11708
|
}
|
|
@@ -11105,7 +11735,7 @@ async function journey(label3, work, home, checks) {
|
|
|
11105
11735
|
`exit ${run2.code} in ${Math.round(run2.ms / 1e3)} s${leaked ? " \xB7 PRINTED A SECRET" : ""}${run2.code !== 0 ? ` \xB7 ${run2.out.trim().split("\n").slice(-2).join(" / ")}` : ""}`
|
|
11106
11736
|
);
|
|
11107
11737
|
if (run2.code !== 0) return null;
|
|
11108
|
-
const env = readEnv(
|
|
11738
|
+
const env = readEnv(import_path11.default.join(work, `${B.cli}-demo`, ".env.local"));
|
|
11109
11739
|
const api = env.APIBLAZE_URL;
|
|
11110
11740
|
const nino = env.APIBLAZE_SERVER_KEY;
|
|
11111
11741
|
const as = (user, key = nino) => ({ "x-api-key": key, "x-end-user-id": user, "content-type": "application/json" });
|
|
@@ -11173,7 +11803,7 @@ async function claimJourney(work, checks, env) {
|
|
|
11173
11803
|
let code = "";
|
|
11174
11804
|
let teamId = "";
|
|
11175
11805
|
try {
|
|
11176
|
-
const st = JSON.parse(
|
|
11806
|
+
const st = JSON.parse(import_fs14.default.readFileSync(import_path11.default.join(work, `${B.cli}-demo`, ".demo", "state.json"), "utf8"));
|
|
11177
11807
|
code = st.claimCode ?? "";
|
|
11178
11808
|
teamId = st.teamId ?? "";
|
|
11179
11809
|
} catch {
|
|
@@ -11236,11 +11866,11 @@ async function claimJourney(work, checks, env) {
|
|
|
11236
11866
|
}
|
|
11237
11867
|
async function runOpAnon(o = {}) {
|
|
11238
11868
|
const t0 = Date.now();
|
|
11239
|
-
const root =
|
|
11240
|
-
const home =
|
|
11241
|
-
const work =
|
|
11242
|
-
|
|
11243
|
-
|
|
11869
|
+
const root = import_fs14.default.mkdtempSync(import_path11.default.join(import_os5.default.tmpdir(), `${B.cli}-op-anon-`));
|
|
11870
|
+
const home = import_path11.default.join(root, "home");
|
|
11871
|
+
const work = import_path11.default.join(root, "work");
|
|
11872
|
+
import_fs14.default.mkdirSync(home);
|
|
11873
|
+
import_fs14.default.mkdirSync(work);
|
|
11244
11874
|
const checks = [];
|
|
11245
11875
|
const log4 = (s) => {
|
|
11246
11876
|
if (!o.json) console.log(s);
|
|
@@ -11252,21 +11882,21 @@ async function runOpAnon(o = {}) {
|
|
|
11252
11882
|
await claimJourney(work, checks, env);
|
|
11253
11883
|
} finally {
|
|
11254
11884
|
const stop = await runCli(["demo", "stop"], work, home, 3e4);
|
|
11255
|
-
const demoFolder =
|
|
11256
|
-
if (
|
|
11885
|
+
const demoFolder = import_path11.default.join(work, `${B.cli}-demo`);
|
|
11886
|
+
if (import_fs14.default.existsSync(import_path11.default.join(demoFolder, "start.js"))) {
|
|
11257
11887
|
await new Promise((res) => {
|
|
11258
|
-
const c = (0, import_child_process8.spawn)(process.execPath, [
|
|
11888
|
+
const c = (0, import_child_process8.spawn)(process.execPath, [import_path11.default.join(demoFolder, "start.js"), "--stop"], { cwd: demoFolder, stdio: "ignore" });
|
|
11259
11889
|
c.on("close", () => res());
|
|
11260
11890
|
});
|
|
11261
11891
|
}
|
|
11262
|
-
const env = readEnv(
|
|
11892
|
+
const env = readEnv(import_path11.default.join(work, `${B.cli}-demo`, ".env.local"));
|
|
11263
11893
|
let portsFree = true;
|
|
11264
11894
|
for (const p of [env.APP_PORT, env.BACKEND_PORT].filter(Boolean)) {
|
|
11265
11895
|
const r = await http(`http://127.0.0.1:${p}/`, { timeoutMs: 2e3 });
|
|
11266
11896
|
if (r.status !== 0) portsFree = false;
|
|
11267
11897
|
}
|
|
11268
11898
|
checks.push({ id: "stop", ok: stop.code === 0 && portsFree, detail: `exit ${stop.code}${portsFree ? "" : " \xB7 ports still answering"}` });
|
|
11269
|
-
|
|
11899
|
+
import_fs14.default.rmSync(root, { recursive: true, force: true });
|
|
11270
11900
|
}
|
|
11271
11901
|
for (const c of checks) log4(` ${c.skipped ? import_chalk76.default.yellow("\u2013") : c.ok ? import_chalk76.default.green("\u2713") : import_chalk76.default.red("\u2717")} ${c.id.padEnd(24)} ${import_chalk76.default.dim(c.detail)}`);
|
|
11272
11902
|
const failed = checks.filter((c) => !c.ok).length;
|
|
@@ -11279,13 +11909,13 @@ async function runOpAnon(o = {}) {
|
|
|
11279
11909
|
if (failed) process.exitCode = 1;
|
|
11280
11910
|
return { ok: !failed, checks };
|
|
11281
11911
|
}
|
|
11282
|
-
var
|
|
11912
|
+
var import_fs14, import_os5, import_path11, import_child_process8, import_chalk76, SECRET, SANDBOX_TEAM;
|
|
11283
11913
|
var init_op_anon = __esm({
|
|
11284
11914
|
"src/commands/op-anon.ts"() {
|
|
11285
11915
|
"use strict";
|
|
11286
|
-
|
|
11916
|
+
import_fs14 = __toESM(require("fs"));
|
|
11287
11917
|
import_os5 = __toESM(require("os"));
|
|
11288
|
-
|
|
11918
|
+
import_path11 = __toESM(require("path"));
|
|
11289
11919
|
import_child_process8 = require("child_process");
|
|
11290
11920
|
import_chalk76 = __toESM(require("chalk"));
|
|
11291
11921
|
init_brand();
|
|
@@ -11298,7 +11928,7 @@ var init_op_anon = __esm({
|
|
|
11298
11928
|
});
|
|
11299
11929
|
|
|
11300
11930
|
// src/index.ts
|
|
11301
|
-
var
|
|
11931
|
+
var import_fs15 = __toESM(require("fs"));
|
|
11302
11932
|
var import_commander = require("commander");
|
|
11303
11933
|
var import_chalk78 = __toESM(require("chalk"));
|
|
11304
11934
|
init_package();
|
|
@@ -11889,8 +12519,8 @@ async function opCall(call3) {
|
|
|
11889
12519
|
}
|
|
11890
12520
|
|
|
11891
12521
|
// src/commands/connector.ts
|
|
11892
|
-
var
|
|
11893
|
-
var
|
|
12522
|
+
var import_fs9 = __toESM(require("fs"));
|
|
12523
|
+
var import_path7 = __toESM(require("path"));
|
|
11894
12524
|
var import_chalk30 = __toESM(require("chalk"));
|
|
11895
12525
|
var import_ora10 = __toESM(require("ora"));
|
|
11896
12526
|
init_caller();
|
|
@@ -12034,7 +12664,7 @@ function suggestedMcpHost(apexDomain) {
|
|
|
12034
12664
|
// src/commands/connector.ts
|
|
12035
12665
|
var PORTALS = {
|
|
12036
12666
|
claude: "https://claude.com/docs/connectors/building/submission",
|
|
12037
|
-
chatgpt: "https://
|
|
12667
|
+
chatgpt: "https://platform.openai.com/plugins"
|
|
12038
12668
|
};
|
|
12039
12669
|
var projectPath3 = (p) => `/projects/${encodeURIComponent(p.projectId)}/${encodeURIComponent(p.apiVersion)}`;
|
|
12040
12670
|
async function patchProject(caller, proj2, body, summary) {
|
|
@@ -12052,11 +12682,15 @@ var PROMPT_FOR_RULE = {
|
|
|
12052
12682
|
VULN_CONTACT: { key: "vulnContact", message: "Security contact (email or URL):" },
|
|
12053
12683
|
LISTING_NAME_LEN_CHATGPT: { key: "serverName", message: "Listing name", limit: 30, portal: "ChatGPT" },
|
|
12054
12684
|
LISTING_NAME_LEN_CLAUDE: { key: "serverName", message: "Listing name", limit: 100, portal: "Claude" },
|
|
12055
|
-
TAGLINE_LEN_CHATGPT: { key: "
|
|
12685
|
+
TAGLINE_LEN_CHATGPT: { key: "shortDescription", message: "ChatGPT subtitle (the Claude tagline stays as it is)", limit: 30, portal: "ChatGPT" },
|
|
12686
|
+
SHORT_DESCRIPTION_SET: { key: "shortDescription", message: "ChatGPT subtitle", limit: 30, portal: "ChatGPT" },
|
|
12056
12687
|
TAGLINE_LEN_CLAUDE: { key: "tagline", message: "One-line tagline", limit: 200, portal: "Claude" },
|
|
12057
12688
|
DESCRIPTION_LEN_CLAUDE: { key: "description", message: "Description", limit: 2e3, portal: "Claude" },
|
|
12058
12689
|
DESCRIPTION_LEN_CHATGPT: { key: "description", message: "Description", limit: 4e3, portal: "ChatGPT" },
|
|
12059
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):" },
|
|
12060
12694
|
// ChatGPT only: the portal issues this token (Plugins → Connect MCP server); we serve it verbatim.
|
|
12061
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):" }
|
|
12062
12696
|
};
|
|
@@ -12073,7 +12707,7 @@ function looksLikeSpecArgument(arg) {
|
|
|
12073
12707
|
if (/^https?:\/\//i.test(a)) return true;
|
|
12074
12708
|
if (/[\\/]/.test(a) || /\.(ya?ml|json)$/i.test(a)) {
|
|
12075
12709
|
try {
|
|
12076
|
-
return
|
|
12710
|
+
return import_fs9.default.existsSync(a) && import_fs9.default.statSync(a).isFile();
|
|
12077
12711
|
} catch {
|
|
12078
12712
|
return false;
|
|
12079
12713
|
}
|
|
@@ -12087,6 +12721,7 @@ function currentListingValue(r, key) {
|
|
|
12087
12721
|
tagline: "tagline",
|
|
12088
12722
|
description: "description",
|
|
12089
12723
|
prompt: "examplePrompts",
|
|
12724
|
+
shortDescription: "shortDescription",
|
|
12090
12725
|
privacyUrl: "privacyUrl",
|
|
12091
12726
|
support: "supportContact",
|
|
12092
12727
|
docsUrl: "docsUrl",
|
|
@@ -12172,14 +12807,14 @@ async function promptForMissing(r, interactive2, defaults = { prompts: [] }, pla
|
|
|
12172
12807
|
if (testPrompt) {
|
|
12173
12808
|
let res = await testPrompt(text);
|
|
12174
12809
|
if (!res.called) {
|
|
12175
|
-
say_(import_chalk30.default.yellow(` \u2013
|
|
12810
|
+
say_(import_chalk30.default.yellow(` \u2013 No tool call for "${text}". A prompt should say what the tool does (a real URL, name or id helps).`));
|
|
12176
12811
|
const again = await inquirer3.prompt([{ type: "input", name: "line", message: "Reword it (Enter keeps it):", default: text, validate: withinLimit(a) }]);
|
|
12177
12812
|
if (again.line?.trim() && again.line.trim() !== text) {
|
|
12178
12813
|
text = again.line.trim();
|
|
12179
12814
|
res = await testPrompt(text);
|
|
12180
12815
|
}
|
|
12181
12816
|
}
|
|
12182
|
-
|
|
12817
|
+
if (!res.called) say_(import_chalk30.default.yellow(" \u2013 still no tool call; kept (the report will say so)"));
|
|
12183
12818
|
}
|
|
12184
12819
|
lines.push(text);
|
|
12185
12820
|
}
|
|
@@ -12193,7 +12828,7 @@ async function promptForMissing(r, interactive2, defaults = { prompts: [] }, pla
|
|
|
12193
12828
|
continue;
|
|
12194
12829
|
}
|
|
12195
12830
|
const dk = DEFAULT_FOR_KEY[a.key];
|
|
12196
|
-
const base2 = typeof current === "string" ? current : dk ? defaults[dk] : void 0;
|
|
12831
|
+
const base2 = typeof current === "string" ? current : a.key === "shortDescription" ? currentListingValue(r, "tagline") : dk ? defaults[dk] : void 0;
|
|
12197
12832
|
const def = typeof base2 === "string" && base2 ? a.limit ? shortenTo(base2, a.limit) : base2 : void 0;
|
|
12198
12833
|
const { value } = await inquirer3.prompt([{
|
|
12199
12834
|
type: "input",
|
|
@@ -12248,7 +12883,7 @@ async function askOpenMode(caller, proj2, r, opts, interactive2, say) {
|
|
|
12248
12883
|
async function promptListingBasics(r, interactive2, defaults, given, platforms = PLATFORMS, save) {
|
|
12249
12884
|
const meta = r.target?.metadata ?? {};
|
|
12250
12885
|
const gpt = platforms.includes("chatgpt");
|
|
12251
|
-
const lim = { serverName: gpt ? 30 : 100, tagline:
|
|
12886
|
+
const lim = { serverName: gpt ? 30 : 100, tagline: 200, description: platforms.includes("claude") ? 2e3 : 4e3 };
|
|
12252
12887
|
const cutTo = (v, n) => v ? shortenTo(v, n) : v;
|
|
12253
12888
|
const all = [
|
|
12254
12889
|
{ key: "serverName", current: meta.serverName, def: cutTo(defaults.serverName, lim.serverName), message: `Listing name (\u2264 ${lim.serverName} chars, no "MCP"/"Plugin" suffix):` },
|
|
@@ -12316,7 +12951,7 @@ async function specTextFor(source) {
|
|
|
12316
12951
|
const res = await fetch(source, { signal: AbortSignal.timeout(8e3) });
|
|
12317
12952
|
return res.ok ? await res.text() : null;
|
|
12318
12953
|
}
|
|
12319
|
-
return
|
|
12954
|
+
return import_fs9.default.readFileSync(source, "utf-8");
|
|
12320
12955
|
} catch {
|
|
12321
12956
|
return null;
|
|
12322
12957
|
}
|
|
@@ -12507,6 +13142,10 @@ async function runConnector(target2, opts) {
|
|
|
12507
13142
|
const changed = await fixToolDescriptions(proj2, r.report.project.environment, interactive2, say);
|
|
12508
13143
|
if (changed) r = await fetchDirectoryReport(caller, proj2, { platforms });
|
|
12509
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
|
+
}
|
|
12510
13149
|
if (await askOpenMode(caller, proj2, r, opts, interactive2, say)) r = await fetchDirectoryReport(caller, proj2, { platforms });
|
|
12511
13150
|
let reviewer = null;
|
|
12512
13151
|
if (r.report.results.some((x) => x.id === "TEST_ACCOUNT_SET" && !x.pass)) {
|
|
@@ -12532,11 +13171,15 @@ async function runConnector(target2, opts) {
|
|
|
12532
13171
|
if (!tester && !testerTried) {
|
|
12533
13172
|
testerTried = true;
|
|
12534
13173
|
const { createPromptTester: createPromptTester2 } = await Promise.resolve().then(() => (init_connector_exercise(), connector_exercise_exports));
|
|
12535
|
-
tester = await createPromptTester2(caller, proj2, { say });
|
|
13174
|
+
tester = await createPromptTester2(caller, proj2, { say, platforms });
|
|
12536
13175
|
}
|
|
12537
13176
|
if (!tester) return { called: true, tools: [] };
|
|
12538
|
-
|
|
12539
|
-
|
|
13177
|
+
const t = tester;
|
|
13178
|
+
say(import_chalk30.default.dim(` trying it through ${t.clients.map((c) => c === "claude" ? "Claude Code" : "Codex CLI").join(" and ")}\u2026`));
|
|
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);
|
|
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`));
|
|
13182
|
+
return res;
|
|
12540
13183
|
};
|
|
12541
13184
|
if (!isReady(r, platforms) || Object.keys(basics).length) {
|
|
12542
13185
|
const asked = { ...basics, ...isReady(r, platforms) ? {} : await promptForMissing(r, interactive2, defaults, platforms, basics, interactive2 ? saveAnswer : void 0, interactive2 && opts.exercise !== false ? testPrompt : void 0) };
|
|
@@ -12557,7 +13200,7 @@ async function runConnector(target2, opts) {
|
|
|
12557
13200
|
}
|
|
12558
13201
|
if (interactive2 && r.report.results.some((x) => ANSWER_RULES.includes(x.id) && !x.pass)) {
|
|
12559
13202
|
const { runConnectorAnswers: runConnectorAnswers2 } = await Promise.resolve().then(() => (init_connector_exercise(), connector_exercise_exports));
|
|
12560
|
-
await runConnectorAnswers2(proj2.
|
|
13203
|
+
await runConnectorAnswers2(proj2.projectId, { team: opts.team ?? proj2.teamId, apiversion: opts.apiversion, platforms: [...platforms], derived: false, testPrompt: opts.exercise !== false ? testPrompt : void 0 });
|
|
12561
13204
|
r = await fetchDirectoryReport(caller, proj2, { platforms });
|
|
12562
13205
|
}
|
|
12563
13206
|
if (opts.exercise !== false) {
|
|
@@ -12568,18 +13211,26 @@ async function runConnector(target2, opts) {
|
|
|
12568
13211
|
}
|
|
12569
13212
|
if (run2) {
|
|
12570
13213
|
const { runConnectorExercise: runConnectorExercise2 } = await Promise.resolve().then(() => (init_connector_exercise(), connector_exercise_exports));
|
|
12571
|
-
const ran = await runConnectorExercise2(proj2.
|
|
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 });
|
|
12572
13215
|
if (interactive2 && ran.failedPrompts.length) {
|
|
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 ")}`));
|
|
12573
13217
|
const { rewritten, prompts } = await rewriteFailedPrompts(r, ran.failedPrompts, saveAnswer, say);
|
|
12574
13218
|
if (rewritten.length) {
|
|
12575
|
-
|
|
12576
|
-
|
|
13219
|
+
const { clientsFor: clientsFor2 } = await Promise.resolve().then(() => (init_connector_exercise(), connector_exercise_exports));
|
|
13220
|
+
say(import_chalk30.default.dim(` Running the ${prompts.length} prompts through ${clientsFor2(platforms).map((c) => c === "claude" ? "Claude Code" : "Codex CLI").join(" and ")} again (the report reads the last run)\u2026`));
|
|
13221
|
+
const again = await runConnectorExercise2(proj2.projectId, { team: opts.team ?? proj2.teamId, apiversion: opts.apiversion, json, noEmail: opts.email === false, report: false, platforms: [...platforms], promptsOnly: prompts, provenPromptNotes: ran.notes.filter((n) => n.kind === "negative") });
|
|
13222
|
+
ran.promptTools = { ...ran.promptTools, ...again.promptTools };
|
|
13223
|
+
ran.codexPromptTools = { ...ran.codexPromptTools, ...again.codexPromptTools };
|
|
13224
|
+
ran.failedPrompts = again.failedPrompts;
|
|
13225
|
+
ran.failedByClient = again.failedByClient;
|
|
12577
13226
|
}
|
|
12578
13227
|
}
|
|
12579
|
-
if (interactive2 || opts.yes) await offerDrafts(caller, proj2, r, ran, interactive2, say);
|
|
12580
|
-
|
|
13228
|
+
if (interactive2 || opts.yes) await offerDrafts(caller, proj2, r, ran, interactive2, say, platforms);
|
|
13229
|
+
const extraProbes = ran?.userinfo && ran.userinfo.userinfoStatus != null ? { metadata: { userinfoStatus: ran.userinfo.userinfoStatus, userinfoEmailVerified: ran.userinfo.userinfoEmailVerified } } : null;
|
|
13230
|
+
r = await fetchDirectoryReport(caller, proj2, { platforms, publishedUrl: r.report.project.publishedUrl ?? null, extraProbes, listing: listingOf(r) });
|
|
12581
13231
|
}
|
|
12582
13232
|
}
|
|
13233
|
+
if (opts.waitLogin !== false && interactive2) r = await waitForClaudeLogin(caller, proj2, r, platforms, interactive2, say);
|
|
12583
13234
|
if (!readyExceptSnapshot(r, platforms)) {
|
|
12584
13235
|
if (!domain2.active && domain2.hostname && !json) {
|
|
12585
13236
|
console.error(import_chalk30.default.yellow(`
|
|
@@ -12587,14 +13238,33 @@ async function runConnector(target2, opts) {
|
|
|
12587
13238
|
}
|
|
12588
13239
|
exitWithReport(r, proj2, platforms, json, interactive2);
|
|
12589
13240
|
}
|
|
12590
|
-
r = await fetchDirectoryReport(caller, proj2, { platforms, refreshSnapshot: true, publishedUrl: r.report.project.publishedUrl ?? null });
|
|
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
|
+
}
|
|
12591
13249
|
const probes = await packetProbes(r);
|
|
12592
13250
|
const packet = await producer(caller, { method: "POST", path: `${projectPath3(proj2)}/directory-packet`, body: { platforms, ...probes ? { probes } : {} }, summary: "Submission packet" });
|
|
12593
13251
|
const dir = opts.out ?? process.cwd();
|
|
12594
|
-
|
|
12595
|
-
const
|
|
12596
|
-
|
|
12597
|
-
|
|
13252
|
+
import_fs9.default.mkdirSync(dir, { recursive: true });
|
|
13253
|
+
const mdPath = import_path7.default.join(dir, `connector-packet-${proj2.projectId}.md`);
|
|
13254
|
+
const jsonPath = import_path7.default.join(dir, `connector-packet-${proj2.projectId}.json`);
|
|
13255
|
+
import_fs9.default.writeFileSync(mdPath, packet.markdown, { mode: 384 });
|
|
13256
|
+
import_fs9.default.writeFileSync(jsonPath, JSON.stringify(packet.json, null, 2) + "\n", { mode: 384 });
|
|
13257
|
+
let zipPath = null;
|
|
13258
|
+
if (platforms.includes("chatgpt")) {
|
|
13259
|
+
const { writePluginPackage: writePluginPackage2 } = await Promise.resolve().then(() => (init_plugin_package(), plugin_package_exports));
|
|
13260
|
+
const manifest = packet.json.chatgpt;
|
|
13261
|
+
if (manifest) {
|
|
13262
|
+
const written = await writePluginPackage2(dir, manifest);
|
|
13263
|
+
if (written.problem) say(import_chalk30.default.yellow(` ${written.problem}`));
|
|
13264
|
+
zipPath = written.zipPath;
|
|
13265
|
+
if (written.seq) await patchProject(caller, proj2, { directory_metadata: { plugin_package_seq: written.seq } }, "Bump the plugin package version").catch(() => null);
|
|
13266
|
+
}
|
|
13267
|
+
}
|
|
12598
13268
|
if (json) {
|
|
12599
13269
|
process.stdout.write(JSON.stringify({ ready: true, project: proj2.projectId, headline: r.rendered.headline, report: r.report, packet: { markdown: mdPath, json: jsonPath }, portals: PORTALS, ...reviewer ? { reviewer } : {} }) + "\n");
|
|
12600
13270
|
return;
|
|
@@ -12607,17 +13277,51 @@ async function runConnector(target2, opts) {
|
|
|
12607
13277
|
}
|
|
12608
13278
|
console.log();
|
|
12609
13279
|
console.log(import_chalk30.default.green.bold(` ${r.rendered.headline}`));
|
|
12610
|
-
console.log(` Submission packet: ${import_chalk30.default.cyan(mdPath)} and ${import_chalk30.default.cyan(jsonPath)}`
|
|
13280
|
+
console.log(` Submission packet: ${import_chalk30.default.cyan(mdPath)} and ${import_chalk30.default.cyan(jsonPath)}${zipPath ? `
|
|
13281
|
+
OpenAI plugin package: ${import_chalk30.default.cyan(zipPath)}` : ""}`);
|
|
12611
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)"));
|
|
12612
|
-
printSubmitSteps(r, mdPath, platforms);
|
|
13283
|
+
printSubmitSteps(r, mdPath, platforms, watching);
|
|
12613
13284
|
console.log(import_chalk30.default.dim(` ${r.report.disclaimer}`));
|
|
12614
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
|
+
}
|
|
12615
13319
|
async function rewriteFailedPrompts(r, failed, save, say) {
|
|
12616
13320
|
const current = currentListingValue(r, "prompt");
|
|
12617
13321
|
const list = Array.isArray(current) ? [...current] : [];
|
|
12618
13322
|
const { default: inquirer3 } = await import("inquirer");
|
|
12619
13323
|
const rewritten = [];
|
|
12620
|
-
say(import_chalk30.default.yellow(` ${failed.length === 1 ? "This prompt" : `${failed.length} prompts`}
|
|
13324
|
+
say(import_chalk30.default.yellow(` ${failed.length === 1 ? "This prompt" : `${failed.length} prompts`} produced no tool call; a prompt should name what the tool does${failed.length === 1 ? "" : ", one at a time"}:`));
|
|
12621
13325
|
for (const old of failed) {
|
|
12622
13326
|
const { value } = await inquirer3.prompt([{ type: "input", name: "value", message: `Rewrite "${old}" (Enter keeps it):`, default: old, validate: (v) => v.trim().length <= 300 ? true : `${v.trim().length} characters; 300 at most` }]);
|
|
12623
13327
|
const next = (value ?? "").trim();
|
|
@@ -12647,12 +13351,12 @@ async function packetProbes(r) {
|
|
|
12647
13351
|
return null;
|
|
12648
13352
|
}
|
|
12649
13353
|
}
|
|
12650
|
-
async function offerDrafts(caller, proj2, r, ran, interactive2, say) {
|
|
13354
|
+
async function offerDrafts(caller, proj2, r, ran, interactive2, say, platforms = PLATFORMS) {
|
|
12651
13355
|
if (!ran || !Array.isArray(ran.rows) || !ran.publishedUrl) return false;
|
|
12652
|
-
const { draftMissing: draftMissing2, ANSWER_MIN: ANSWER_MIN2 } = await Promise.resolve().then(() => (init_portal_drafts(), portal_drafts_exports));
|
|
13356
|
+
const { draftMissing: draftMissing2, draftChatgptTestCases: draftChatgptTestCases2, ANSWER_MIN: ANSWER_MIN2 } = await Promise.resolve().then(() => (init_portal_drafts(), portal_drafts_exports));
|
|
12653
13357
|
const meta = r.target?.metadata ?? {};
|
|
12654
13358
|
const prompts = (meta.examplePrompts ?? []).filter((p) => typeof p === "string" && !!p.trim());
|
|
12655
|
-
const
|
|
13359
|
+
const input = {
|
|
12656
13360
|
publishedUrl: ran.publishedUrl,
|
|
12657
13361
|
open: ran.open,
|
|
12658
13362
|
reviewerUsername: ran.reviewerUsername,
|
|
@@ -12660,15 +13364,32 @@ async function offerDrafts(caller, proj2, r, ran, interactive2, say) {
|
|
|
12660
13364
|
rows: ran.rows,
|
|
12661
13365
|
prompts,
|
|
12662
13366
|
promptTools: ran.promptTools,
|
|
13367
|
+
codexPromptTools: ran.codexPromptTools ?? {},
|
|
12663
13368
|
serverName: meta.serverName,
|
|
12664
13369
|
tagline: meta.tagline,
|
|
12665
|
-
description: meta.description
|
|
12666
|
-
|
|
12667
|
-
|
|
12668
|
-
|
|
13370
|
+
description: meta.description,
|
|
13371
|
+
platforms,
|
|
13372
|
+
negativePrompts: meta.portalAnswers?.chatgpt_test_cases?.negative ?? []
|
|
13373
|
+
};
|
|
12669
13374
|
let saved = false;
|
|
13375
|
+
if (platforms.includes("chatgpt") && Object.keys(ran.codexPromptTools ?? {}).length) {
|
|
13376
|
+
const cases = draftChatgptTestCases2(input);
|
|
13377
|
+
const have = (meta.portalAnswers?.chatgpt_test_cases?.positive ?? []).length;
|
|
13378
|
+
if (cases.positive.length && cases.positive.length !== have) {
|
|
13379
|
+
try {
|
|
13380
|
+
await patchProject(caller, proj2, { directory_portal_answers: { chatgpt_test_cases: cases } }, "Save OpenAI's test cases");
|
|
13381
|
+
saved = true;
|
|
13382
|
+
say(import_chalk30.default.green(` \u2713 OpenAI's positive test cases written from the Codex run: ${cases.positive.length}/5${cases.positive.length < 5 ? " (more prompts that call a tool are needed for five)" : ""}`));
|
|
13383
|
+
} catch (e) {
|
|
13384
|
+
say(import_chalk30.default.yellow(` not saved: ${e instanceof Error ? e.message : String(e)}`));
|
|
13385
|
+
}
|
|
13386
|
+
}
|
|
13387
|
+
}
|
|
13388
|
+
const drafts = platforms.includes("claude") ? draftMissing2(input, { use_cases: meta.portalAnswers?.use_cases, reviewer_instructions: meta.portalAnswers?.reviewer_instructions, description: meta.description }) : draftMissing2(input, { use_cases: "x".repeat(ANSWER_MIN2.use_cases), reviewer_instructions: "x".repeat(ANSWER_MIN2.reviewer_instructions), description: meta.description });
|
|
13389
|
+
if (!drafts.length) return saved;
|
|
13390
|
+
const inquirer3 = interactive2 ? (await import("inquirer")).default : null;
|
|
12670
13391
|
say("");
|
|
12671
|
-
say(import_chalk30.default.bold(` Written from the run \u2014 the portal's ${drafts.map((d) => d.label.toLowerCase()).join(", ")}
|
|
13392
|
+
say(import_chalk30.default.bold(` Written from the run \u2014 the portal's ${drafts.map((d) => d.label.toLowerCase()).join(", ")}:`));
|
|
12672
13393
|
for (const d of drafts) {
|
|
12673
13394
|
let text = d.draft;
|
|
12674
13395
|
say("");
|
|
@@ -12694,10 +13415,10 @@ async function offerDrafts(caller, proj2, r, ran, interactive2, say) {
|
|
|
12694
13415
|
}
|
|
12695
13416
|
var SNAPSHOT_RULE = "LISTING_SNAPSHOT_MATCH";
|
|
12696
13417
|
var withProject = (s, projectId) => s.replace(/<project>/g, projectId);
|
|
12697
|
-
var ANSWER_RULES = ["PORTAL_ANSWERS_COMPLETE", "HEALTH_SPONSORED_DECLARED", "API_OWNERSHIP_DOMAIN"];
|
|
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"];
|
|
12698
13419
|
function exitWithReport(r, proj2, platforms, json, interactive2 = false) {
|
|
12699
13420
|
if (json) {
|
|
12700
|
-
|
|
13421
|
+
writeJsonLine({ ready: false, project: proj2.projectId, headline: r.rendered.headline, report: r.report });
|
|
12701
13422
|
process.exit(1);
|
|
12702
13423
|
}
|
|
12703
13424
|
printReport(r, false);
|
|
@@ -12708,11 +13429,77 @@ function exitWithReport(r, proj2, platforms, json, interactive2 = false) {
|
|
|
12708
13429
|
console.error(import_chalk30.default.red(`
|
|
12709
13430
|
Not ready: ${blocking.length} blocker${blocking.length === 1 ? "" : "s"} (${blocking.map((x) => x.id).join(", ")}).`));
|
|
12710
13431
|
const name = proj2.projectId;
|
|
12711
|
-
if (
|
|
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
|
+
}
|
|
13436
|
+
const answerish = real.filter((x) => ANSWER_RULES.includes(x.id));
|
|
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)}`);
|
|
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\`.`);
|
|
12712
13439
|
else if (first) console.error(` Next: ${withProject(first.fix ?? first.message ?? first.title, name)}`);
|
|
12713
13440
|
console.error(import_chalk30.default.dim(` Report page: ${DASHBOARD_BASE6}/directory/${encodeURIComponent(proj2.projectId)}/${encodeURIComponent(proj2.apiVersion)}`));
|
|
12714
13441
|
process.exit(1);
|
|
12715
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
|
+
}
|
|
12716
13503
|
var TOOL_DESC_RULE = "TOOL_DESC_NONEMPTY_SPECIFIC";
|
|
12717
13504
|
var UTILITY_PATH = /(health|ping|status|openapi|swagger|docs|spec|whoami|version)/i;
|
|
12718
13505
|
var SPECIFIC_MIN = 12;
|
|
@@ -12725,7 +13512,10 @@ function needsDescription(t) {
|
|
|
12725
13512
|
if (!d || d.length < SPECIFIC_MIN) return true;
|
|
12726
13513
|
const m = (t["x-route"]?.method ?? "get").toUpperCase();
|
|
12727
13514
|
const p = t["x-route"]?.path ?? "";
|
|
12728
|
-
|
|
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;
|
|
12729
13519
|
}
|
|
12730
13520
|
function derivedDescription(t) {
|
|
12731
13521
|
const m = (t["x-route"]?.method ?? "get").toUpperCase();
|
|
@@ -12735,9 +13525,40 @@ function derivedDescription(t) {
|
|
|
12735
13525
|
const plural = segments.filter((s) => !s.startsWith("{")).pop()?.replace(/[-_]/g, " ") ?? "result";
|
|
12736
13526
|
const one = segments[segments.length - 1]?.startsWith("{");
|
|
12737
13527
|
const noun = one ? plural.replace(/s$/, "") : plural;
|
|
12738
|
-
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.`;
|
|
12739
13533
|
return `${summary.replace(/\.$/, "")}. ${m} ${p}. ${returns}`;
|
|
12740
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
|
+
}
|
|
12741
13562
|
async function fixToolDescriptions(proj2, environment, interactive2, say) {
|
|
12742
13563
|
const { loadCatalogue: loadCatalogue2, saveCatalogue: saveCatalogue2 } = await Promise.resolve().then(() => (init_mcp(), mcp_exports));
|
|
12743
13564
|
const t = { projectId: proj2.projectId, apiVersion: proj2.apiVersion, environment: environment || "dev" };
|
|
@@ -12825,17 +13646,17 @@ async function headStatus(u) {
|
|
|
12825
13646
|
return 0;
|
|
12826
13647
|
}
|
|
12827
13648
|
}
|
|
12828
|
-
function printSubmitSteps(r, mdPath, platforms = PLATFORMS) {
|
|
13649
|
+
function printSubmitSteps(r, mdPath, platforms = PLATFORMS, watching = false) {
|
|
12829
13650
|
const url = r.report.project.publishedUrl ?? "(the MCP URL in the packet)";
|
|
12830
13651
|
const open = isOpenMode(r);
|
|
12831
13652
|
const claude = platforms.includes("claude");
|
|
12832
13653
|
const chatgpt = platforms.includes("chatgpt");
|
|
12833
|
-
const where = [claude ? "Claude (Settings \u2192 Connectors)" : null, chatgpt ? "ChatGPT (
|
|
13654
|
+
const where = [claude ? "Claude (Settings \u2192 Connectors)" : null, chatgpt ? "ChatGPT (chatgpt.com/plugins \u2192 Add custom MCP server)" : null].filter(Boolean).join(" and in ");
|
|
12834
13655
|
const steps = [
|
|
12835
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.`,
|
|
12836
|
-
...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(
|
|
12837
|
-
...chatgpt ? [`ChatGPT: ${import_chalk30.default.underline(PORTALS.chatgpt)} \u2192
|
|
12838
|
-
open ? "A nightly re-check emails
|
|
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))}.`] : [],
|
|
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.`] : [],
|
|
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."
|
|
12839
13660
|
];
|
|
12840
13661
|
console.log();
|
|
12841
13662
|
console.log(import_chalk30.default.bold(" Submit (you do this part; the packet has every value to paste):"));
|
|
@@ -12848,15 +13669,15 @@ init_connector_exercise();
|
|
|
12848
13669
|
init_connector_docs();
|
|
12849
13670
|
|
|
12850
13671
|
// src/commands/skill.ts
|
|
12851
|
-
var
|
|
13672
|
+
var import_fs12 = __toESM(require("fs"));
|
|
12852
13673
|
var import_os4 = __toESM(require("os"));
|
|
12853
|
-
var
|
|
13674
|
+
var import_path10 = __toESM(require("path"));
|
|
12854
13675
|
var import_chalk34 = __toESM(require("chalk"));
|
|
12855
13676
|
init_brand();
|
|
12856
13677
|
|
|
12857
13678
|
// src/lib/detect-backend.ts
|
|
12858
|
-
var
|
|
12859
|
-
var
|
|
13679
|
+
var import_fs10 = __toESM(require("fs"));
|
|
13680
|
+
var import_path8 = __toESM(require("path"));
|
|
12860
13681
|
var import_net = __toESM(require("net"));
|
|
12861
13682
|
var NODE_SERVERS = [
|
|
12862
13683
|
["express", "Express", 3e3],
|
|
@@ -12887,9 +13708,9 @@ var COMMON_PORTS = [3e3, 3001, 4e3, 5e3, 8e3, 8080, 8787];
|
|
|
12887
13708
|
var SPEC_PATHS = ["/openapi.json", "/openapi.yaml", "/docs/openapi.json", "/swagger.json", "/api/openapi.json"];
|
|
12888
13709
|
function readText(file2, max = 256 * 1024) {
|
|
12889
13710
|
try {
|
|
12890
|
-
const st =
|
|
13711
|
+
const st = import_fs10.default.statSync(file2);
|
|
12891
13712
|
if (!st.isFile() || st.size > max) return null;
|
|
12892
|
-
return
|
|
13713
|
+
return import_fs10.default.readFileSync(file2, "utf8");
|
|
12893
13714
|
} catch {
|
|
12894
13715
|
return null;
|
|
12895
13716
|
}
|
|
@@ -12898,7 +13719,7 @@ function findSpecFile(dir) {
|
|
|
12898
13719
|
for (const d of SPEC_DIRS) {
|
|
12899
13720
|
for (const n of SPEC_NAMES) {
|
|
12900
13721
|
const rel = d === "." ? n : `${d}/${n}`;
|
|
12901
|
-
const text = readText(
|
|
13722
|
+
const text = readText(import_path8.default.join(dir, rel));
|
|
12902
13723
|
if (text && /\b(openapi|swagger)\s*["']?\s*:/.test(text)) {
|
|
12903
13724
|
const m = /\b(?:localhost|127\.0\.0\.1):(\d{2,5})\b/.exec(text);
|
|
12904
13725
|
return { file: `./${rel}`, ...m ? { port: Number(m[1]) } : {} };
|
|
@@ -12915,7 +13736,7 @@ function portFromScripts(scripts) {
|
|
|
12915
13736
|
return void 0;
|
|
12916
13737
|
}
|
|
12917
13738
|
function fromNode(dir, out) {
|
|
12918
|
-
const text = readText(
|
|
13739
|
+
const text = readText(import_path8.default.join(dir, "package.json"));
|
|
12919
13740
|
if (!text) return;
|
|
12920
13741
|
let pkg = {};
|
|
12921
13742
|
try {
|
|
@@ -12981,7 +13802,7 @@ function portInSource(text) {
|
|
|
12981
13802
|
}
|
|
12982
13803
|
function fromEntryFiles(dir, out) {
|
|
12983
13804
|
for (const rel of ENTRY_FILES) {
|
|
12984
|
-
const text = readText(
|
|
13805
|
+
const text = readText(import_path8.default.join(dir, rel));
|
|
12985
13806
|
if (!text) continue;
|
|
12986
13807
|
let fw;
|
|
12987
13808
|
let def;
|
|
@@ -13018,7 +13839,7 @@ function fromEntryFiles(dir, out) {
|
|
|
13018
13839
|
}
|
|
13019
13840
|
function fromPython(dir, out) {
|
|
13020
13841
|
for (const f2 of ["requirements.txt", "pyproject.toml", "Pipfile"]) {
|
|
13021
|
-
const text = readText(
|
|
13842
|
+
const text = readText(import_path8.default.join(dir, f2));
|
|
13022
13843
|
if (!text) continue;
|
|
13023
13844
|
const hit = PY_SERVERS.find(([re]) => re.test(text));
|
|
13024
13845
|
if (hit) {
|
|
@@ -13030,7 +13851,7 @@ function fromPython(dir, out) {
|
|
|
13030
13851
|
}
|
|
13031
13852
|
}
|
|
13032
13853
|
function fromOthers(dir, out) {
|
|
13033
|
-
const gomod = readText(
|
|
13854
|
+
const gomod = readText(import_path8.default.join(dir, "go.mod"));
|
|
13034
13855
|
if (gomod) {
|
|
13035
13856
|
const hit = GO_SERVERS.find(([re]) => re.test(gomod));
|
|
13036
13857
|
out.framework = hit ? hit[1] : "a Go server";
|
|
@@ -13038,7 +13859,7 @@ function fromOthers(dir, out) {
|
|
|
13038
13859
|
out.defaultPort = 8080;
|
|
13039
13860
|
return;
|
|
13040
13861
|
}
|
|
13041
|
-
const gemfile = readText(
|
|
13862
|
+
const gemfile = readText(import_path8.default.join(dir, "Gemfile"));
|
|
13042
13863
|
if (gemfile) {
|
|
13043
13864
|
const hit = RUBY_SERVERS.find(([re]) => re.test(gemfile));
|
|
13044
13865
|
if (hit) {
|
|
@@ -13049,7 +13870,7 @@ function fromOthers(dir, out) {
|
|
|
13049
13870
|
}
|
|
13050
13871
|
}
|
|
13051
13872
|
for (const f2 of ["pom.xml", "build.gradle", "build.gradle.kts"]) {
|
|
13052
|
-
const text = readText(
|
|
13873
|
+
const text = readText(import_path8.default.join(dir, f2));
|
|
13053
13874
|
if (text && /spring-boot/.test(text)) {
|
|
13054
13875
|
out.framework = "Spring Boot";
|
|
13055
13876
|
out.source = f2;
|
|
@@ -13085,7 +13906,7 @@ async function liveSpec(port) {
|
|
|
13085
13906
|
async function detectBackend(dir, opts = {}) {
|
|
13086
13907
|
const out = {};
|
|
13087
13908
|
try {
|
|
13088
|
-
if (
|
|
13909
|
+
if (import_fs10.default.existsSync(import_path8.default.join(dir, ".demo", "state.json"))) out.isDemo = true;
|
|
13089
13910
|
fromNode(dir, out);
|
|
13090
13911
|
if (!out.framework) fromPython(dir, out);
|
|
13091
13912
|
if (!out.framework) fromOthers(dir, out);
|
|
@@ -13117,7 +13938,7 @@ function backendPort(f2) {
|
|
|
13117
13938
|
// src/commands/skill.ts
|
|
13118
13939
|
init_api();
|
|
13119
13940
|
var SKILL_NAME = "ship-safely";
|
|
13120
|
-
var SKILL_RELATIVE_PATH =
|
|
13941
|
+
var SKILL_RELATIVE_PATH = import_path10.default.join(".claude", "skills", SKILL_NAME, "SKILL.md");
|
|
13121
13942
|
var CP_TOOLS = [
|
|
13122
13943
|
"list_proxies",
|
|
13123
13944
|
"propose_access_rules",
|
|
@@ -13191,9 +14012,9 @@ function staleSkillCopies(cwd = process.cwd(), home = import_os4.default.homedir
|
|
|
13191
14012
|
const current = skillMarkdown();
|
|
13192
14013
|
const out = [];
|
|
13193
14014
|
for (const dir of /* @__PURE__ */ new Set([cwd, home])) {
|
|
13194
|
-
const f2 =
|
|
14015
|
+
const f2 = import_path10.default.join(dir, SKILL_RELATIVE_PATH);
|
|
13195
14016
|
try {
|
|
13196
|
-
if (
|
|
14017
|
+
if (import_fs12.default.readFileSync(f2, "utf8") !== current) out.push(f2);
|
|
13197
14018
|
} catch {
|
|
13198
14019
|
}
|
|
13199
14020
|
}
|
|
@@ -13739,13 +14560,13 @@ async function runSkill(opts = {}) {
|
|
|
13739
14560
|
}
|
|
13740
14561
|
function installSkill(opts) {
|
|
13741
14562
|
const md = skillMarkdown();
|
|
13742
|
-
const target2 =
|
|
13743
|
-
if (
|
|
14563
|
+
const target2 = import_path10.default.resolve(process.cwd(), SKILL_RELATIVE_PATH);
|
|
14564
|
+
if (import_fs12.default.existsSync(target2) && !opts.force) {
|
|
13744
14565
|
console.error(import_chalk34.default.yellow(`${SKILL_RELATIVE_PATH} already exists \u2014 re-run with --force to overwrite it.`));
|
|
13745
14566
|
process.exit(1);
|
|
13746
14567
|
}
|
|
13747
|
-
|
|
13748
|
-
|
|
14568
|
+
import_fs12.default.mkdirSync(import_path10.default.dirname(target2), { recursive: true });
|
|
14569
|
+
import_fs12.default.writeFileSync(target2, md, "utf8");
|
|
13749
14570
|
console.log(`${import_chalk34.default.green("\u2713")} Installed the ${import_chalk34.default.bold(SKILL_NAME)} skill: ${import_chalk34.default.bold(target2)}`);
|
|
13750
14571
|
console.log(import_chalk34.default.dim(` Claude Code picks it up in this project. Say "ship this API" and it runs npx ${B.cli}@latest dev / create for you.`));
|
|
13751
14572
|
}
|
|
@@ -13876,8 +14697,8 @@ async function runLogout(opts = {}) {
|
|
|
13876
14697
|
|
|
13877
14698
|
// src/commands/flush.ts
|
|
13878
14699
|
var import_chalk37 = __toESM(require("chalk"));
|
|
13879
|
-
var
|
|
13880
|
-
var
|
|
14700
|
+
var fs21 = __toESM(require("fs"));
|
|
14701
|
+
var path17 = __toESM(require("path"));
|
|
13881
14702
|
init_auth();
|
|
13882
14703
|
init_external_mcp();
|
|
13883
14704
|
async function runFlush(opts) {
|
|
@@ -13885,7 +14706,7 @@ async function runFlush(opts) {
|
|
|
13885
14706
|
const demos = await stopDemos2().catch(() => 0);
|
|
13886
14707
|
if (demos) console.log(import_chalk37.default.dim(` Stopped ${demos} running demo(s).`));
|
|
13887
14708
|
const dir = getApiblazeDir();
|
|
13888
|
-
const entries2 =
|
|
14709
|
+
const entries2 = fs21.existsSync(dir) ? fs21.readdirSync(dir) : [];
|
|
13889
14710
|
if (!entries2.length) {
|
|
13890
14711
|
console.log(import_chalk37.default.dim("Nothing to flush \u2014 no local apiblaze config found."));
|
|
13891
14712
|
return;
|
|
@@ -13898,7 +14719,7 @@ async function runFlush(opts) {
|
|
|
13898
14719
|
const footprint = apiblazeMcpFootprint();
|
|
13899
14720
|
const mdFiles = ["claude", "codex"].map((k) => ({ k, file: agentInstructionFile(k) })).filter(({ file: file2 }) => {
|
|
13900
14721
|
try {
|
|
13901
|
-
return
|
|
14722
|
+
return fs21.readFileSync(file2, "utf-8").includes("<!-- apiblaze:mcp:");
|
|
13902
14723
|
} catch {
|
|
13903
14724
|
return false;
|
|
13904
14725
|
}
|
|
@@ -13907,7 +14728,7 @@ async function runFlush(opts) {
|
|
|
13907
14728
|
console.log(import_chalk37.default.dim(` \u2022 MCP servers installed in other tools: ${[...footprint.claude, ...footprint.codex].join(", ")}`));
|
|
13908
14729
|
}
|
|
13909
14730
|
if (mdFiles.length) {
|
|
13910
|
-
console.log(import_chalk37.default.dim(` \u2022 the "use this MCP" blocks in ${mdFiles.map((m) =>
|
|
14731
|
+
console.log(import_chalk37.default.dim(` \u2022 the "use this MCP" blocks in ${mdFiles.map((m) => path17.basename(m.file)).join(" and ")} (here)`));
|
|
13911
14732
|
}
|
|
13912
14733
|
console.log(import_chalk37.default.dim(" It does NOT delete server-side proxies/keys \u2014 those GC or are revoked separately."));
|
|
13913
14734
|
if (!opts.yes) {
|
|
@@ -13923,7 +14744,7 @@ async function runFlush(opts) {
|
|
|
13923
14744
|
let removed = 0;
|
|
13924
14745
|
for (const e of entries2) {
|
|
13925
14746
|
try {
|
|
13926
|
-
|
|
14747
|
+
fs21.rmSync(path17.join(dir, e), { recursive: true, force: true });
|
|
13927
14748
|
removed++;
|
|
13928
14749
|
} catch (err) {
|
|
13929
14750
|
console.log(import_chalk37.default.red(` \u2717 could not remove ${e}: ${err instanceof Error ? err.message : "error"}`));
|
|
@@ -13938,14 +14759,14 @@ async function runFlush(opts) {
|
|
|
13938
14759
|
for (const { k, file: file2 } of mdFiles) {
|
|
13939
14760
|
let text = "";
|
|
13940
14761
|
try {
|
|
13941
|
-
text =
|
|
14762
|
+
text = fs21.readFileSync(file2, "utf-8");
|
|
13942
14763
|
} catch {
|
|
13943
14764
|
continue;
|
|
13944
14765
|
}
|
|
13945
14766
|
const names = [...text.matchAll(/<!-- apiblaze:mcp:([^\s>]+) -->/g)].map((m) => m[1]);
|
|
13946
14767
|
let cleared = 0;
|
|
13947
14768
|
for (const n of new Set(names)) if (removeAgentInstruction(k, n)) cleared++;
|
|
13948
|
-
if (cleared) console.log(` ${import_chalk37.default.dim("\u2022")} removed ${cleared} APIblaze block(s) from ${import_chalk37.default.bold(
|
|
14769
|
+
if (cleared) console.log(` ${import_chalk37.default.dim("\u2022")} removed ${cleared} APIblaze block(s) from ${import_chalk37.default.bold(path17.basename(file2))}`);
|
|
13949
14770
|
}
|
|
13950
14771
|
console.log(`${import_chalk37.default.green("\u2714")} Flushed \u2014 removed ${removed} item(s). You're fully logged out and clean.`);
|
|
13951
14772
|
console.log(import_chalk37.default.dim(" Next `apiblaze create` starts a brand-new workspace with nothing carried over."));
|
|
@@ -14592,7 +15413,7 @@ function describeJob(job) {
|
|
|
14592
15413
|
}
|
|
14593
15414
|
async function pollJob(jobId, opts = {}) {
|
|
14594
15415
|
const started = Date.now();
|
|
14595
|
-
const
|
|
15416
|
+
const path20 = jobPath(jobId, opts.teamId);
|
|
14596
15417
|
let lastPhase = "";
|
|
14597
15418
|
if (!opts.quiet) process.stdout.write(import_chalk45.default.dim(opts.label ?? "Deleting"));
|
|
14598
15419
|
for (; ; ) {
|
|
@@ -14604,7 +15425,7 @@ async function pollJob(jobId, opts = {}) {
|
|
|
14604
15425
|
);
|
|
14605
15426
|
}
|
|
14606
15427
|
await new Promise((r) => setTimeout(r, POLL_MS));
|
|
14607
|
-
const job = await admin({ method: "GET", path:
|
|
15428
|
+
const job = await admin({ method: "GET", path: path20, summary: "poll cascade job" });
|
|
14608
15429
|
if (!opts.quiet) {
|
|
14609
15430
|
const phase = describeJob(job);
|
|
14610
15431
|
if (phase !== lastPhase) {
|
|
@@ -14749,7 +15570,7 @@ async function runJobStatus(jobId, opts) {
|
|
|
14749
15570
|
|
|
14750
15571
|
// src/commands/export.ts
|
|
14751
15572
|
var import_chalk48 = __toESM(require("chalk"));
|
|
14752
|
-
var
|
|
15573
|
+
var import_fs13 = require("fs");
|
|
14753
15574
|
init_admin();
|
|
14754
15575
|
init_auth();
|
|
14755
15576
|
init_resolve();
|
|
@@ -14798,7 +15619,7 @@ async function runExport(projectArg, versionArg, opts) {
|
|
|
14798
15619
|
});
|
|
14799
15620
|
if (!res.ok) throw new Error(`Download failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
14800
15621
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
14801
|
-
(0,
|
|
15622
|
+
(0, import_fs13.writeFileSync)(outFile, buf);
|
|
14802
15623
|
console.log(import_chalk48.default.green(`\u2713 Saved ${outFile} (${(buf.length / 1024).toFixed(0)} KB)`));
|
|
14803
15624
|
if (job.decryption_key) console.log(import_chalk48.default.yellow(`Decryption key (shown once): ${job.decryption_key}`));
|
|
14804
15625
|
console.log(import_chalk48.default.dim("Read MIGRATION-REPORT.json inside the zip for the full fidelity ledger."));
|
|
@@ -14855,8 +15676,8 @@ function resolveRecipeName(raw, githubHandle, proxyName) {
|
|
|
14855
15676
|
function normalizeName3(raw) {
|
|
14856
15677
|
return (raw || "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
14857
15678
|
}
|
|
14858
|
-
async function publicFetch(
|
|
14859
|
-
const res = await fetch(`${RECIPES_BASE}/api/recipes${
|
|
15679
|
+
async function publicFetch(path20) {
|
|
15680
|
+
const res = await fetch(`${RECIPES_BASE}/api/recipes${path20}`, {
|
|
14860
15681
|
headers: { accept: "application/json" }
|
|
14861
15682
|
}).catch((err) => {
|
|
14862
15683
|
throw new Error(`Could not reach the recipe registry at ${RECIPES_BASE} \u2014 ${err.message}`);
|
|
@@ -14909,11 +15730,11 @@ async function fetchRecipeFile(ref, caller) {
|
|
|
14909
15730
|
Private recipes are visible only to the publisher's team \u2014 run \`apiblaze login\` if you are on it.`
|
|
14910
15731
|
);
|
|
14911
15732
|
}
|
|
14912
|
-
const
|
|
15733
|
+
const path20 = revision !== void 0 ? `/recipes/${ref.handle}/${ref.slug}/${revision}` : `/recipes/${ref.handle}/${ref.slug}/file`;
|
|
14913
15734
|
try {
|
|
14914
15735
|
return await producer(caller, {
|
|
14915
15736
|
method: "GET",
|
|
14916
|
-
path:
|
|
15737
|
+
path: path20,
|
|
14917
15738
|
summary: `Read private recipe ${ref.name}${revision !== void 0 ? `@${revision}` : ""}`
|
|
14918
15739
|
});
|
|
14919
15740
|
} catch (err) {
|
|
@@ -16057,9 +16878,9 @@ function printEnvelopeViolations(violations) {
|
|
|
16057
16878
|
console.log("bring-your-own-key case. Change the rules above, then publish again.");
|
|
16058
16879
|
console.log();
|
|
16059
16880
|
}
|
|
16060
|
-
async function askPromotion(
|
|
16881
|
+
async function askPromotion(path20) {
|
|
16061
16882
|
const { default: inquirer3 } = await import("inquirer");
|
|
16062
|
-
const suggestedId =
|
|
16883
|
+
const suggestedId = path20.split(/[.[\]]/).filter(Boolean).slice(-2).join("_").toLowerCase().replace(/[^a-z0-9_]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "") || "value";
|
|
16063
16884
|
const answers = await inquirer3.prompt([
|
|
16064
16885
|
{ type: "input", name: "prompt", message: " prompt them with:", validate: (v) => v.trim() ? true : "Required." },
|
|
16065
16886
|
{ type: "input", name: "example", message: " example:" },
|
|
@@ -16070,7 +16891,7 @@ async function askPromotion(path19) {
|
|
|
16070
16891
|
const id = String(answers.id || suggestedId).toLowerCase().replace(/[^a-z0-9_]/g, "_");
|
|
16071
16892
|
console.log(import_chalk51.default.dim(` \u2192 replaced with {{${id}}}`));
|
|
16072
16893
|
return {
|
|
16073
|
-
path:
|
|
16894
|
+
path: path20,
|
|
16074
16895
|
id,
|
|
16075
16896
|
prompt: String(answers.prompt).trim(),
|
|
16076
16897
|
...answers.example ? { example: String(answers.example).trim() } : {},
|
|
@@ -17844,7 +18665,7 @@ async function providersMenu(cBase, clientLabel) {
|
|
|
17844
18665
|
}
|
|
17845
18666
|
|
|
17846
18667
|
// src/commands/spec.ts
|
|
17847
|
-
var
|
|
18668
|
+
var fs22 = __toESM(require("fs"));
|
|
17848
18669
|
var import_chalk62 = __toESM(require("chalk"));
|
|
17849
18670
|
var import_ora27 = __toESM(require("ora"));
|
|
17850
18671
|
init_admin();
|
|
@@ -17867,7 +18688,7 @@ async function runSpecSet(project, opts) {
|
|
|
17867
18688
|
}
|
|
17868
18689
|
let specContent;
|
|
17869
18690
|
try {
|
|
17870
|
-
specContent =
|
|
18691
|
+
specContent = fs22.readFileSync(opts.file, "utf-8");
|
|
17871
18692
|
} catch {
|
|
17872
18693
|
console.error(import_chalk62.default.red(`Cannot read file: ${opts.file}`));
|
|
17873
18694
|
process.exit(1);
|
|
@@ -18344,7 +19165,7 @@ async function patchSetting(proj2, s, value, cfg) {
|
|
|
18344
19165
|
}
|
|
18345
19166
|
const body = s.toPatch(value, cfg);
|
|
18346
19167
|
const method = s.putLimits ? "PUT" : "PATCH";
|
|
18347
|
-
const
|
|
19168
|
+
const path20 = s.putLimits ? `/projects/${proj2.projectId}/${proj2.apiVersion}/limits` : `/projects/${proj2.projectId}/${proj2.apiVersion}`;
|
|
18348
19169
|
if (s.needsTenant) {
|
|
18349
19170
|
const tenant2 = proj2.tenant ?? cfg.tenant ?? cfg.default_tenant;
|
|
18350
19171
|
if (!tenant2) throw new Error(`Could not tell which tenant ${proj2.projectName} serves \u2014 pass it via \`apiblaze config ${proj2.projectName} --tenant <slug>\` is not supported yet; run \`apiblaze projects\` and check the project has a tenant.`);
|
|
@@ -18354,7 +19175,7 @@ async function patchSetting(proj2, s, value, cfg) {
|
|
|
18354
19175
|
try {
|
|
18355
19176
|
await admin({
|
|
18356
19177
|
method,
|
|
18357
|
-
path:
|
|
19178
|
+
path: path20,
|
|
18358
19179
|
body,
|
|
18359
19180
|
summary: `Set ${s.key} = ${JSON.stringify(value)}`
|
|
18360
19181
|
});
|
|
@@ -19217,11 +20038,11 @@ async function resolveTransport(opts) {
|
|
|
19217
20038
|
const token = getAccessToken();
|
|
19218
20039
|
return {
|
|
19219
20040
|
tenant: tenant2,
|
|
19220
|
-
call: async (
|
|
20041
|
+
call: async (path20, method = "GET", body) => {
|
|
19221
20042
|
const res = await fetch(`${DASHBOARD_BASE11}/api/cli/iam`, {
|
|
19222
20043
|
method: "POST",
|
|
19223
20044
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
19224
|
-
body: JSON.stringify({ tenant: tenant2, path:
|
|
20045
|
+
body: JSON.stringify({ tenant: tenant2, path: path20, method, body })
|
|
19225
20046
|
});
|
|
19226
20047
|
const data = await res.json().catch(() => ({}));
|
|
19227
20048
|
if (!res.ok) throw new ApiError(res.status, data?.details ?? data?.error ?? res.statusText, data);
|
|
@@ -19239,8 +20060,8 @@ async function resolveTransport(opts) {
|
|
|
19239
20060
|
return {
|
|
19240
20061
|
tenant: fresh.tenant,
|
|
19241
20062
|
selfEmail: fresh.email,
|
|
19242
|
-
call: async (
|
|
19243
|
-
const res = await fetch(`${IAM_BASE}${
|
|
20063
|
+
call: async (path20, method = "GET", body) => {
|
|
20064
|
+
const res = await fetch(`${IAM_BASE}${path20}`, {
|
|
19244
20065
|
method,
|
|
19245
20066
|
headers: {
|
|
19246
20067
|
"Content-Type": "application/json",
|
|
@@ -19692,28 +20513,28 @@ async function runConsumerApikeys(opts) {
|
|
|
19692
20513
|
// src/commands/sidecar.ts
|
|
19693
20514
|
var import_chalk68 = __toESM(require("chalk"));
|
|
19694
20515
|
var import_ora33 = __toESM(require("ora"));
|
|
19695
|
-
var
|
|
19696
|
-
var
|
|
20516
|
+
var fs23 = __toESM(require("fs"));
|
|
20517
|
+
var path18 = __toESM(require("path"));
|
|
19697
20518
|
init_admin();
|
|
19698
20519
|
init_resolve();
|
|
19699
20520
|
init_auth();
|
|
19700
20521
|
function detectNextProject(root) {
|
|
19701
|
-
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)));
|
|
19702
20523
|
let hasDep = false;
|
|
19703
20524
|
try {
|
|
19704
|
-
const pkg = JSON.parse(
|
|
20525
|
+
const pkg = JSON.parse(fs23.readFileSync(path18.join(root, "package.json"), "utf8"));
|
|
19705
20526
|
hasDep = !!(pkg.dependencies?.next || pkg.devDependencies?.next);
|
|
19706
20527
|
} catch {
|
|
19707
20528
|
}
|
|
19708
|
-
const appDir =
|
|
19709
|
-
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"));
|
|
19710
20531
|
return { found: hasConfig || hasDep || appDir || pagesDir, router: appDir ? "app" : pagesDir ? "pages" : null };
|
|
19711
20532
|
}
|
|
19712
20533
|
function upsertEnvLocal(root, token) {
|
|
19713
|
-
const p =
|
|
20534
|
+
const p = path18.join(root, ".env.local");
|
|
19714
20535
|
let existing = "";
|
|
19715
20536
|
try {
|
|
19716
|
-
existing =
|
|
20537
|
+
existing = fs23.readFileSync(p, "utf8");
|
|
19717
20538
|
} catch {
|
|
19718
20539
|
}
|
|
19719
20540
|
const had = /^APIBLAZE_API_KEY=/m.test(existing) || /^APIBLAZE_TOKEN=/m.test(existing);
|
|
@@ -19728,15 +20549,15 @@ function upsertEnvLocal(root, token) {
|
|
|
19728
20549
|
next = (next.endsWith("\n") ? next : next + "\n") + `APIBLAZE_SIDECAR_VERBOSE=true
|
|
19729
20550
|
`;
|
|
19730
20551
|
}
|
|
19731
|
-
|
|
20552
|
+
fs23.writeFileSync(p, next);
|
|
19732
20553
|
return had ? "rotated" : "created";
|
|
19733
20554
|
}
|
|
19734
20555
|
function installSidecarPackage(root) {
|
|
19735
|
-
if (
|
|
20556
|
+
if (fs23.existsSync(path18.join(root, "node_modules", "apiblaze", "package.json"))) {
|
|
19736
20557
|
console.log(` ${import_chalk68.default.green("\u2713")} apiblaze package already installed`);
|
|
19737
20558
|
return;
|
|
19738
20559
|
}
|
|
19739
|
-
const has = (f2) =>
|
|
20560
|
+
const has = (f2) => fs23.existsSync(path18.join(root, f2));
|
|
19740
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" };
|
|
19741
20562
|
const spinner = (0, import_ora33.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
|
|
19742
20563
|
try {
|
|
@@ -19749,7 +20570,7 @@ function installSidecarPackage(root) {
|
|
|
19749
20570
|
}
|
|
19750
20571
|
function readEnvKey(root) {
|
|
19751
20572
|
try {
|
|
19752
|
-
const s =
|
|
20573
|
+
const s = fs23.readFileSync(path18.join(root, ".env.local"), "utf8");
|
|
19753
20574
|
const m = s.match(/^APIBLAZE_API_KEY=(.+)$/m) ?? s.match(/^APIBLAZE_TOKEN=(.+)$/m);
|
|
19754
20575
|
return m ? m[1].trim() : null;
|
|
19755
20576
|
} catch {
|
|
@@ -19757,16 +20578,16 @@ function readEnvKey(root) {
|
|
|
19757
20578
|
}
|
|
19758
20579
|
}
|
|
19759
20580
|
function ensureGitignored(root) {
|
|
19760
|
-
const p =
|
|
20581
|
+
const p = path18.join(root, ".gitignore");
|
|
19761
20582
|
let c = "";
|
|
19762
20583
|
try {
|
|
19763
|
-
c =
|
|
20584
|
+
c = fs23.readFileSync(p, "utf8");
|
|
19764
20585
|
} catch {
|
|
19765
20586
|
}
|
|
19766
|
-
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");
|
|
19767
20588
|
}
|
|
19768
20589
|
function wireInstrumentation(root) {
|
|
19769
|
-
const existing = ["instrumentation.ts", "instrumentation.js",
|
|
20590
|
+
const existing = ["instrumentation.ts", "instrumentation.js", path18.join("src", "instrumentation.ts")].map((c) => path18.join(root, c)).find((f2) => fs23.existsSync(f2));
|
|
19770
20591
|
const body = `import { register as apiblaze } from "apiblaze/sidecar";
|
|
19771
20592
|
|
|
19772
20593
|
export function register() {
|
|
@@ -19774,18 +20595,18 @@ export function register() {
|
|
|
19774
20595
|
}
|
|
19775
20596
|
`;
|
|
19776
20597
|
if (!existing) {
|
|
19777
|
-
|
|
20598
|
+
fs23.writeFileSync(path18.join(root, "instrumentation.ts"), body);
|
|
19778
20599
|
return "created";
|
|
19779
20600
|
}
|
|
19780
|
-
const cur =
|
|
20601
|
+
const cur = fs23.readFileSync(existing, "utf8");
|
|
19781
20602
|
if (cur.includes("apiblaze/sidecar")) return "present";
|
|
19782
20603
|
if (/export\s+function\s+register\s*\(/.test(cur)) {
|
|
19783
|
-
|
|
20604
|
+
fs23.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
|
|
19784
20605
|
` + cur.replace(/export\s+function\s+register\s*\(\s*\)\s*\{/, (m) => `${m}
|
|
19785
20606
|
apiblaze();`));
|
|
19786
20607
|
return "patched";
|
|
19787
20608
|
}
|
|
19788
|
-
|
|
20609
|
+
fs23.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
|
|
19789
20610
|
${cur}
|
|
19790
20611
|
// call apiblaze() inside your register() export.
|
|
19791
20612
|
`);
|
|
@@ -19868,17 +20689,17 @@ export default async function Page() {
|
|
|
19868
20689
|
function generateInspector(root, router) {
|
|
19869
20690
|
try {
|
|
19870
20691
|
if (router === "pages") {
|
|
19871
|
-
const dir2 =
|
|
19872
|
-
const f3 =
|
|
19873
|
-
|
|
19874
|
-
return
|
|
19875
|
-
}
|
|
19876
|
-
const base2 =
|
|
19877
|
-
const dir =
|
|
19878
|
-
|
|
19879
|
-
const f2 =
|
|
19880
|
-
|
|
19881
|
-
return
|
|
20692
|
+
const dir2 = fs23.existsSync(path18.join(root, "src", "pages")) ? path18.join(root, "src", "pages") : path18.join(root, "pages");
|
|
20693
|
+
const f3 = path18.join(dir2, "abz-inspector.tsx");
|
|
20694
|
+
fs23.writeFileSync(f3, INSPECTOR_PAGE);
|
|
20695
|
+
return path18.relative(root, f3);
|
|
20696
|
+
}
|
|
20697
|
+
const base2 = fs23.existsSync(path18.join(root, "src", "app")) ? path18.join(root, "src", "app") : path18.join(root, "app");
|
|
20698
|
+
const dir = path18.join(base2, "abz-inspector");
|
|
20699
|
+
fs23.mkdirSync(dir, { recursive: true });
|
|
20700
|
+
const f2 = path18.join(dir, "page.tsx");
|
|
20701
|
+
fs23.writeFileSync(f2, INSPECTOR_PAGE);
|
|
20702
|
+
return path18.relative(root, f2);
|
|
19882
20703
|
} catch {
|
|
19883
20704
|
return null;
|
|
19884
20705
|
}
|
|
@@ -19918,7 +20739,7 @@ async function runAnonymousInit(root, router, opts) {
|
|
|
19918
20739
|
console.log(import_chalk68.default.dim(` From another machine: apiblaze claim ${out.claim_code} \xB7 expires in 30 days`));
|
|
19919
20740
|
}
|
|
19920
20741
|
async function runSidecar(opts) {
|
|
19921
|
-
const root =
|
|
20742
|
+
const root = path18.resolve(opts.dir ?? process.cwd());
|
|
19922
20743
|
const detected2 = detectNextProject(root);
|
|
19923
20744
|
if (!detected2.found) {
|
|
19924
20745
|
console.log(import_chalk68.default.yellow(`No Next.js project detected in ${root}.`));
|
|
@@ -19977,7 +20798,7 @@ async function runSidecar(opts) {
|
|
|
19977
20798
|
console.log(` 2. The origins your app calls appear as ${import_chalk68.default.bold("candidates")} \u2014 list them: ${import_chalk68.default.cyan("apiblaze sidecar")}`);
|
|
19978
20799
|
console.log(` 3. Approve the ones to route: ${import_chalk68.default.cyan("apiblaze sidecar approve api.stripe.com")} (or in the dashboard)`);
|
|
19979
20800
|
console.log(` \u2026within ~5 min your app starts routing that origin through APIblaze.`);
|
|
19980
|
-
if (inspectorPath) console.log(` \u2022 Try it now: open ${import_chalk68.default.underline("http://localhost:3000/abz-inspector")} (dev only; rm ${
|
|
20801
|
+
if (inspectorPath) console.log(` \u2022 Try it now: open ${import_chalk68.default.underline("http://localhost:3000/abz-inspector")} (dev only; rm ${path18.dirname(inspectorPath)} before shipping)`);
|
|
19981
20802
|
if (switchingTeam) console.log(import_chalk68.default.dim(` \u2022 Approved origins are per-team \u2014 re-approve them on ${teamName ?? teamId} with \`apiblaze sidecar approve <origin>\`.`));
|
|
19982
20803
|
console.log("");
|
|
19983
20804
|
console.log(import_chalk68.default.dim(" Manage: apiblaze sidecar (list/approve/deny/remove)"));
|
|
@@ -20688,17 +21509,17 @@ var import_chalk74 = __toESM(require("chalk"));
|
|
|
20688
21509
|
init_consumer_auth();
|
|
20689
21510
|
var SANDBOX_TENANT = "portalteststenant";
|
|
20690
21511
|
var SANDBOX_PROXY = "portaltestproxypreapproved@2.7.0";
|
|
20691
|
-
function proxyUrlFor(spec2,
|
|
21512
|
+
function proxyUrlFor(spec2, path20 = "/get") {
|
|
20692
21513
|
const [name, version2 = "1.0.0"] = spec2.split("@");
|
|
20693
|
-
return `https://${name}.abz.run/${version2}/prod${
|
|
21514
|
+
return `https://${name}.abz.run/${version2}/prod${path20.startsWith("/") ? path20 : `/${path20}`}`;
|
|
20694
21515
|
}
|
|
20695
21516
|
function isPortalAudience(aud) {
|
|
20696
21517
|
return /^https:\/\/[a-z0-9-]+\.portal\.apiblaze\.com\b/i.test(aud);
|
|
20697
21518
|
}
|
|
20698
21519
|
var log2 = (s) => process.stderr.write(s + "\n");
|
|
20699
|
-
function probeUrlFor(audience,
|
|
21520
|
+
function probeUrlFor(audience, path20 = "/") {
|
|
20700
21521
|
const base2 = audience.replace(/\/+$/, "");
|
|
20701
|
-
return `${base2}/prod${
|
|
21522
|
+
return `${base2}/prod${path20.startsWith("/") ? path20 : `/${path20}`}`;
|
|
20702
21523
|
}
|
|
20703
21524
|
function tamper(token) {
|
|
20704
21525
|
const parts = token.split(".");
|
|
@@ -21630,7 +22451,7 @@ program.command("login").description(`Authenticate with ${B.product}`).option("-
|
|
|
21630
22451
|
process.exit(1);
|
|
21631
22452
|
}
|
|
21632
22453
|
});
|
|
21633
|
-
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) => {
|
|
21634
22455
|
try {
|
|
21635
22456
|
const compliant = opts.compliant ?? opts.policy;
|
|
21636
22457
|
if (compliant !== void 0 && compliant !== false) {
|
|
@@ -21650,15 +22471,15 @@ function collect(value, previous) {
|
|
|
21650
22471
|
return [...previous ?? [], value];
|
|
21651
22472
|
}
|
|
21652
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 })));
|
|
21653
|
-
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("--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) => {
|
|
21654
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>`);
|
|
21655
22476
|
await runConnector(project, opts);
|
|
21656
22477
|
}));
|
|
21657
|
-
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) => {
|
|
21658
22479
|
await runConnectorExercise(project, { ...withConnectorOpts(opts), noClaude: opts.claude === false, noEmail: opts.email === false });
|
|
21659
22480
|
}));
|
|
21660
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))));
|
|
21661
|
-
connector.command("answers").description("The portal's free-text steps 3\u20138 (use cases, prerequisites, access, categories, company, data handling, health data, sponsored content, reviewer instructions): asked when missing, or passed as flags").argument("<project>", `The ${B.thingShort} (project name)`).option("--use-cases <text>", "Use cases (200+ characters)").option("--prerequisites <text>", "What a user needs before it works (one sentence)").option("--access <read|write|both>", "What the tools do").option("--category <name>", "A category suggestion (repeatable, up to five; you pick the closest in the portal)", collect, []).option("--company-name <name>", "Company name").option("--company-website <url>", "Company website (https)").option("--company-contact <email|url>", "Primary contact").option("--reviewer-instructions <text>", "How to add it, sign in, one prompt per tool and what to expect (400+ characters)").option("--data-handling <mode>", "own \xB7 proxied (add --data-handling-statement) \xB7 third-party").option("--data-handling-statement <text>", "For proxied: the permission you hold").option("--health-data <yes|no>", "The tools handle health data").option("--sponsored-content <yes|no>", "Tool results carry sponsored content").option("-y, --yes", "No questions: record the flags only").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) => runConnectorAnswers(project, withConnectorOpts(opts))));
|
|
22482
|
+
connector.command("answers").description("The portal's free-text steps 3\u20138 (use cases, prerequisites, access, categories, company, data handling, health data, sponsored content, reviewer instructions): asked when missing, or passed as flags").argument("<project>", `The ${B.thingShort} (project name)`).option("--use-cases <text>", "Use cases (200+ characters)").option("--prerequisites <text>", "What a user needs before it works (one sentence)").option("--access <read|write|both>", "What the tools do").option("--category <name>", "A category suggestion (repeatable, up to five; you pick the closest in the portal)", collect, []).option("--company-name <name>", "Company name").option("--company-website <url>", "Company website (https)").option("--company-contact <email|url>", "Primary contact").option("--reviewer-instructions <text>", "How to add it, sign in, one prompt per tool and what to expect (400+ characters)").option("--data-handling <mode>", "own \xB7 proxied (add --data-handling-statement) \xB7 third-party").option("--data-handling-statement <text>", "For proxied: the permission you hold").option("--health-data <yes|no>", "The tools handle health data").option("--sponsored-content <yes|no>", "Tool results carry sponsored content").option("--website-url <url>", "OpenAI's form: the public website (https)").option("--support-url <url>", "OpenAI's form: the support page (https)").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)").option("--chatgpt-category <name>", "OpenAI's form: the category, from their list").option("--demo-recording-url <url>", "OpenAI's form: the demo recording URL").option("--release-notes <text>", "OpenAI's form: release notes").option("--short-description <text>", "OpenAI's form: the 30-character subtitle").option("--countries <codes>", "OpenAI's form: ISO country codes, comma-separated", (v) => v.split(",").map((s) => s.trim()).filter(Boolean)).option("--negative-prompt <text>", "OpenAI's form: a prompt the plugin must not act on (repeatable; three)", collect, []).option("-y, --yes", "No questions: record the flags only").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) => runConnectorAnswers(project, withConnectorOpts(opts))));
|
|
21662
22483
|
var withConnectorOpts = (opts) => ({ ...connector.opts(), ...opts ?? {} });
|
|
21663
22484
|
connector.command("reviewer").description("Create the reviewer test account (username + password sign-in on the tenant, no MFA), store it encrypted for the packet, show it once").argument("<project>", `The ${B.thingShort} (project name)`).option("--username <name>", "Account name (default reviewer-<project>)").option("--password <pw>", "Use this password instead of a generated one").option("--seed", "Then open the agent chat with a first message that seeds realistic data through the proxy (billed per turn)").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 (the password appears there exactly once)").action(action((project, opts) => runConnectorReviewer(project, withConnectorOpts(opts))));
|
|
21664
22485
|
program.command("integration").description(`What to add to your frontend and frontend-server for ${B.cli === "mcpblaze" ? "an MCP server" : "a proxy"} \u2014 the same kit the ${B.product} MCP hands an agent: widgets, the call-through with the key + X-End-User-Id, what your backend must trust, env vars, what stays open`).argument("<name>", `The ${B.thingShort} (project name)`).option("--stack <stack>", "nextjs | express | fastapi | other", "nextjs").option("--team <id|name>", "Team the project is in (defaults to your active team)").option("--json", "Print the raw kit as JSON").action(action((name, opts) => runIntegration(name, opts)));
|
|
@@ -21687,11 +22508,11 @@ mcpCommon(mcpPrompts.command("set").description("Add or replace a prompt from a
|
|
|
21687
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)));
|
|
21688
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");
|
|
21689
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)));
|
|
21690
|
-
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)));
|
|
21691
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)));
|
|
21692
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)));
|
|
21693
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) => {
|
|
21694
|
-
const looksLikeSource = !!project && (/^https?:\/\//i.test(project) || /\.(ya?ml|json)$/i.test(project) ||
|
|
22515
|
+
const looksLikeSource = !!project && (/^https?:\/\//i.test(project) || /\.(ya?ml|json)$/i.test(project) || import_fs15.default.existsSync(project));
|
|
21695
22516
|
const target2 = opts.target ?? (looksLikeSource ? project : void 0);
|
|
21696
22517
|
return runApichat({ ...opts, target: target2, project: looksLikeSource ? void 0 : project, openapispec: opts.openapispec ?? opts.openapi });
|
|
21697
22518
|
}));
|