apiblaze 0.21.73 → 0.21.75

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +268 -51
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -34,7 +34,7 @@ var __toESM = (mod, isNodeMode, target2) => (target2 = mod != null ? __create(__
34
34
  var version;
35
35
  var init_package = __esm({
36
36
  "package.json"() {
37
- version = "0.21.73";
37
+ version = "0.21.75";
38
38
  }
39
39
  });
40
40
 
@@ -4243,9 +4243,9 @@ async function openServerProxy(project) {
4243
4243
  }
4244
4244
  function discoverLocalSpecs() {
4245
4245
  const cwd = process.cwd();
4246
- const known = ["openapi.yaml", "openapi.yml", "openapi.json", "swagger.yaml", "swagger.yml", "swagger.json", "api.yaml", "api.yml", "api.json"];
4246
+ const known2 = ["openapi.yaml", "openapi.yml", "openapi.json", "swagger.yaml", "swagger.yml", "swagger.json", "api.yaml", "api.yml", "api.json"];
4247
4247
  const found = [];
4248
- for (const n of known) {
4248
+ for (const n of known2) {
4249
4249
  try {
4250
4250
  if (fs9.statSync(path9.join(cwd, n)).isFile()) found.push(n);
4251
4251
  } catch {
@@ -5549,8 +5549,12 @@ __export(directory_exports, {
5549
5549
  OPENAI_CATEGORIES: () => OPENAI_CATEGORIES,
5550
5550
  PLATFORMS: () => PLATFORMS,
5551
5551
  STEP_IDS: () => STEP_IDS,
5552
+ askLine: () => askLine,
5552
5553
  attestationId: () => attestationId,
5553
5554
  blockerLines: () => blockerLines,
5555
+ ccServerName: () => ccServerName,
5556
+ claudeCodeTests: () => claudeCodeTests,
5557
+ claudeCodeText: () => claudeCodeText,
5554
5558
  compliantGiven: () => compliantGiven,
5555
5559
  directoryBodyFromFlags: () => directoryBodyFromFlags,
5556
5560
  doneLine: () => doneLine,
@@ -5568,10 +5572,12 @@ __export(directory_exports, {
5568
5572
  printProgressTrailer: () => printProgressTrailer,
5569
5573
  printReport: () => printReport,
5570
5574
  scorecardText: () => scorecardText,
5575
+ shellQuote: () => shellQuote2,
5571
5576
  stepOf: () => stepOf,
5572
5577
  stepOpen: () => stepOpen,
5573
5578
  stepRunnable: () => stepRunnable,
5574
5579
  summaryOf: () => summaryOf,
5580
+ withBrandSource: () => withBrandSource,
5575
5581
  writeJsonLine: () => writeJsonLine
5576
5582
  });
5577
5583
  function attestationId(flag) {
@@ -5669,9 +5675,15 @@ function directoryBodyFromFlags(f2) {
5669
5675
  }
5670
5676
  return out;
5671
5677
  }
5678
+ function withBrandSource(url, cli = B.cli) {
5679
+ return cli === "mcpblaze" ? `${url}?source=mcpblazeCli` : url;
5680
+ }
5672
5681
  function pageLinkOf(r, proj2, anchor) {
5673
5682
  const base2 = r?.progress?.pageUrl ?? `${DASHBOARD_BASE6}/directory/${encodeURIComponent(proj2.projectId)}/${encodeURIComponent(proj2.apiVersion)}`;
5674
- return anchor ? `${base2}${anchor.startsWith("#") ? anchor : `#${anchor}`}` : base2;
5683
+ const team = proj2.teamId && !proj2.teamId.startsWith("team_anon_") ? proj2.teamId : null;
5684
+ let page = withBrandSource(base2);
5685
+ if (team) page += `${page.includes("?") ? "&" : "?"}team=${encodeURIComponent(team)}`;
5686
+ return anchor ? `${page}${anchor.startsWith("#") ? anchor : `#${anchor}`}` : page;
5675
5687
  }
5676
5688
  function stepOf(r, id) {
5677
5689
  return r?.progress?.steps.find((s) => s.id === id) ?? null;
@@ -5796,7 +5808,39 @@ function blockerLines(r, platforms) {
5796
5808
  function isApiError(e, status2) {
5797
5809
  return e instanceof ApiError && (status2 === void 0 || e.status === status2);
5798
5810
  }
5799
- var import_node_fs, import_chalk17, PLATFORMS, ATTESTATIONS, OPENAI_CATEGORIES, AUTH_MODES, DATA_HANDLING_MODES, STEP_IDS, reportPath, DESCRIPTION_MIN;
5811
+ function claudeCodeTests(r, proj2) {
5812
+ const url = r?.report?.project?.publishedUrl;
5813
+ if (!url) return null;
5814
+ const name = ccServerName(proj2.projectId);
5815
+ const t = r?.target;
5816
+ const seen = /* @__PURE__ */ new Set();
5817
+ const cases = [];
5818
+ const add = (prompt, expect, refuse) => {
5819
+ const p = (prompt ?? "").trim();
5820
+ if (!p || seen.has(p)) return;
5821
+ seen.add(p);
5822
+ cases.push({ prompt: p, line: askLine(name, p), expect, refuse });
5823
+ };
5824
+ for (const p of t?.metadata?.examplePrompts ?? []) add(p, "it calls your tools and answers from what comes back", false);
5825
+ const tc = t?.metadata?.portalAnswers?.chatgpt_test_cases;
5826
+ for (const c of tc?.positive ?? []) add(c.prompt, c.expected_behavior || "it calls your tools and answers from what comes back", false);
5827
+ for (const c of tc?.negative ?? []) add(c.prompt, `it says it can't help${c.description ? ` (${c.description})` : ""}, and calls no tool`, true);
5828
+ const open = t?.project?.authMode === "open";
5829
+ return {
5830
+ add: `claude mcp add --transport http ${name} ${url}`,
5831
+ signIn: open ? null : `claude, then /mcp \u2192 ${name} \u2192 Authenticate, and sign in as reviewer-${proj2.projectId} (the test account); /exit after`,
5832
+ cases
5833
+ };
5834
+ }
5835
+ function claudeCodeText(cc) {
5836
+ const lines = ["Test each use case in Claude Code", ` 1. ${cc.add}`];
5837
+ if (cc.signIn) lines.push(` 2. Sign in once: ${cc.signIn}`);
5838
+ const n = cc.signIn ? 3 : 2;
5839
+ if (!cc.cases.length) lines.push(` ${n}. Add example prompts to the listing first: each becomes a line here.`);
5840
+ else lines.push(` ${n}. One line per use case:`, ...cc.cases.flatMap((c) => [` ${c.line}`, ` expect: ${c.expect}`]));
5841
+ return lines.join("\n");
5842
+ }
5843
+ var import_node_fs, import_chalk17, PLATFORMS, ATTESTATIONS, OPENAI_CATEGORIES, AUTH_MODES, DATA_HANDLING_MODES, STEP_IDS, reportPath, DESCRIPTION_MIN, ccServerName, shellQuote2, askLine;
5800
5844
  var init_directory = __esm({
5801
5845
  "src/lib/directory.ts"() {
5802
5846
  "use strict";
@@ -5806,6 +5850,7 @@ var init_directory = __esm({
5806
5850
  init_op_call();
5807
5851
  init_directory_probes();
5808
5852
  init_types();
5853
+ init_brand();
5809
5854
  PLATFORMS = ["claude", "chatgpt"];
5810
5855
  ATTESTATIONS = [
5811
5856
  // Claude's Compliance step: seven acknowledgments (submission page, 2026-10-16).
@@ -5829,6 +5874,9 @@ var init_directory = __esm({
5829
5874
  STEP_IDS = ["mode", "address", "access", "tools", "listing", "answers", "attestations", "prove", "signin", "token", "packet", "submit"];
5830
5875
  reportPath = (proj2) => `/projects/${encodeURIComponent(proj2.projectId)}/${encodeURIComponent(proj2.apiVersion)}/directory-report`;
5831
5876
  DESCRIPTION_MIN = 300;
5877
+ ccServerName = (projectId) => projectId.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "app";
5878
+ shellQuote2 = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
5879
+ askLine = (name, prompt) => `claude -p ${shellQuote2(prompt)} --allowedTools ${shellQuote2(`mcp__${name}`)}`;
5832
5880
  }
5833
5881
  });
5834
5882
 
@@ -6612,7 +6660,31 @@ async function printDevOutcome(o, port) {
6612
6660
  }
6613
6661
  if (o.anon) printLegalNotice(" ");
6614
6662
  }
6663
+ function printListingOutcome(o) {
6664
+ const where = { tenant: o.tenant, anon: o.anon };
6665
+ const mcp = mcpUrl(o.name, o.version, o.env, where);
6666
+ const s = o.secure;
6667
+ const applied = s?.applied ?? [];
6668
+ console.log();
6669
+ console.log(` ${import_chalk20.default.green("\u2713")} ${B.thing} ${import_chalk20.default.bold(o.name)}: ${import_chalk20.default.bold(mcp)}`);
6670
+ if (applied.length && s?.enforced) console.log(` ${import_chalk20.default.green("\u2713")} Each person can only change the ${describeFamilies(applied)} they made ${import_chalk20.default.dim("(lists stay open: your backend filters them)")}`);
6671
+ else if (applied.length && s?.pending) console.log(` ${import_chalk20.default.dim("\u2013")} ${describeFamilies(applied)}: the who-can-change-what rules are being applied`);
6672
+ else console.log(` ${import_chalk20.default.dim("\u2013")} ${import_chalk20.default.dim(`Who can change what: not set yet. Later: npx ${B.cli} rule ${o.name}`)}`);
6673
+ if (o.auth.doors.includes("api_key") && o.apiKey) {
6674
+ const saved = process.stdout.isTTY ? null : saveEnvLocal(process.cwd(), {
6675
+ APIBLAZE_URL: restUrl(o.name, o.version, o.env, where),
6676
+ APIBLAZE_MCP_URL: mcp,
6677
+ APIBLAZE_SERVER_KEY: o.apiKey,
6678
+ ...o.tenant ? { APIBLAZE_TENANT: o.tenant } : {}
6679
+ });
6680
+ if (saved?.ok) console.log(` ${import_chalk20.default.green("\u2713")} Backend key saved to .env.local as APIBLAZE_SERVER_KEY`);
6681
+ else console.log(` ${import_chalk20.default.green("\u2713")} Backend key ${import_chalk20.default.dim("(shown once, save it):")} ${import_chalk20.default.bold.green(o.apiKey)}`);
6682
+ }
6683
+ if (o.claim?.claimCode || o.claim?.claimUrl) console.log(import_chalk20.default.dim(` Anonymous: it expires in 30 days unless you sign in (npx ${B.cli} login claims it).`));
6684
+ if (o.anon) printLegalNotice(" ");
6685
+ }
6615
6686
  async function printOutcome(o) {
6687
+ if (o.listing) return printListingOutcome(o);
6616
6688
  const where = { tenant: o.tenant, anon: o.anon };
6617
6689
  const mcp = mcpUrl(o.name, o.version, o.env, where);
6618
6690
  const shown = BRAND === "mcpblaze" ? mcp : B.liveUrl(o.name, o.version, o.env, where);
@@ -6706,8 +6778,11 @@ async function runCreate(opts = {}) {
6706
6778
  opts.target = void 0;
6707
6779
  }
6708
6780
  }
6709
- const creds = loadCredentials();
6781
+ const saved = loadCredentials();
6782
+ const expired = !!saved && Date.now() >= saved.expiresAt && !process.env.APIBLAZE_TOKEN?.trim();
6783
+ const creds = expired ? null : saved;
6710
6784
  if (!creds) {
6785
+ if (expired && !opts.json) console.log(import_chalk20.default.dim(`Your saved login has expired, so this is made anonymously. \`${B.cli} login\` then claims it into your team.`));
6711
6786
  await runAnonymousCreate(opts);
6712
6787
  return;
6713
6788
  }
@@ -6893,6 +6968,7 @@ Create an ${B.thing}
6893
6968
  return;
6894
6969
  }
6895
6970
  await finishSetup({
6971
+ listing: !!opts.directoryReady,
6896
6972
  name,
6897
6973
  version: version2,
6898
6974
  env: "dev",
@@ -7128,7 +7204,8 @@ Create an ${B.thing}`));
7128
7204
  });
7129
7205
  }
7130
7206
  if (name) await applyCreateToggles(name, opts);
7131
- const directory = anonProjectId ? await directoryAfterCreate(anonProjectId, version2, opts) : null;
7207
+ if (anonProjectId) opts.onCreated?.({ name: anonProjectId, version: version2, tenant, teamId: result.team_id });
7208
+ const directory = anonProjectId && !opts.skipDirectoryReport ? await directoryAfterCreate(anonProjectId, version2, opts) : null;
7132
7209
  if (opts.json) {
7133
7210
  process.stdout.write(JSON.stringify({
7134
7211
  project_id: result.project_id,
@@ -7152,6 +7229,7 @@ Create an ${B.thing}`));
7152
7229
  return;
7153
7230
  }
7154
7231
  await finishSetup({
7232
+ listing: !!opts.directoryReady,
7155
7233
  name: anonProjectId,
7156
7234
  version: version2,
7157
7235
  env: "prod",
@@ -7967,10 +8045,10 @@ function orderTools(tools) {
7967
8045
  const rank = { read: 0, create: 1, update: 2, write: 3, delete: 4 };
7968
8046
  return [...tools].sort((a, b) => rank[toolRole(a)] - rank[toolRole(b)] || a.name.localeCompare(b.name));
7969
8047
  }
7970
- function exampleValue(name, p, known = {}, given = {}) {
8048
+ function exampleValue(name, p, known2 = {}, given = {}) {
7971
8049
  if (given[name] !== void 0) return given[name];
7972
8050
  if (p.example !== void 0 && p.example !== null) return p.example;
7973
- if (known[name] !== void 0) return known[name];
8051
+ if (known2[name] !== void 0) return known2[name];
7974
8052
  if (Array.isArray(p.enum) && p.enum.length) return p.enum[0];
7975
8053
  const t = (p.type ?? "string").toLowerCase();
7976
8054
  const fmt = (p.format ?? "").toLowerCase();
@@ -7987,14 +8065,14 @@ function exampleValue(name, p, known = {}, given = {}) {
7987
8065
  if (/(^|_)(q|query|search|term|text|name|title)$/i.test(name)) return "test";
7988
8066
  return "example";
7989
8067
  }
7990
- function validArgs(t, known = {}, given = {}) {
8068
+ function validArgs(t, known2 = {}, given = {}) {
7991
8069
  const out = {};
7992
8070
  const props = t.inputProperties ?? {};
7993
8071
  const pathParams = new Set([...(t.path ?? "").matchAll(/\{([^}]+)\}/g)].map((m) => m[1]));
7994
8072
  for (const [name, p] of Object.entries(props)) {
7995
- const wanted = p.required || pathParams.has(name) || p.example !== void 0 && p.example !== null || known[name] !== void 0 || given[name] !== void 0;
8073
+ const wanted = p.required || pathParams.has(name) || p.example !== void 0 && p.example !== null || known2[name] !== void 0 || given[name] !== void 0;
7996
8074
  if (!wanted) continue;
7997
- out[name] = exampleValue(name, p, known, given);
8075
+ out[name] = exampleValue(name, p, known2, given);
7998
8076
  }
7999
8077
  return out;
8000
8078
  }
@@ -8010,11 +8088,11 @@ function candidateFromPrompts(name, p, prompts) {
8010
8088
  const quoted = text.match(/["“']([^"”']{1,80})["”']/);
8011
8089
  return quoted ? quoted[1].trim() : null;
8012
8090
  }
8013
- function invalidArgs(t, known = {}, given = {}) {
8091
+ function invalidArgs(t, known2 = {}, given = {}) {
8014
8092
  const props = t.inputProperties ?? {};
8015
8093
  const names = Object.keys(props);
8016
8094
  if (!names.length) return null;
8017
- const base2 = validArgs(t, known, given);
8095
+ const base2 = validArgs(t, known2, given);
8018
8096
  const withEnum = names.find((n) => Array.isArray(props[n].enum) && props[n].enum.length);
8019
8097
  if (withEnum) return { args: { ...base2, [withEnum]: "not-a-valid-choice" }, broke: `${withEnum}: out of enum` };
8020
8098
  const typed = names.find((n) => ["integer", "number", "boolean"].includes((props[n].type ?? "").toLowerCase()));
@@ -8248,7 +8326,7 @@ function draftUseCases(i) {
8248
8326
  }
8249
8327
  function draftReviewerInstructions(i) {
8250
8328
  const lines = [];
8251
- 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)";
8329
+ const where = i.platforms?.length === 1 && i.platforms[0] === "chatgpt" ? "ChatGPT (chatgpt.com/plugins \u2192 Add custom MCP server)" : i.platforms?.includes("chatgpt") ? "Claude (Customize > Connectors \u2192 Add custom connector) or ChatGPT (chatgpt.com/plugins \u2192 Add custom MCP server)" : "Claude (Customize > Connectors \u2192 Add custom connector)";
8252
8330
  lines.push(`Add ${i.publishedUrl} as a custom connector in ${where}.`);
8253
8331
  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.`);
8254
8332
  const seen = /* @__PURE__ */ new Set();
@@ -8375,7 +8453,7 @@ function detectCodex(spawn5 = import_child_process5.spawnSync) {
8375
8453
  return { installed: true, loggedIn: st.status === 0 && !/not logged in/i.test(text) };
8376
8454
  }
8377
8455
  function reportLink(projectId, apiVersion = "1.0.0") {
8378
- return `${DASHBOARD_BASE8}/directory/${encodeURIComponent(projectId)}/${encodeURIComponent(apiVersion)}`;
8456
+ return withBrandSource(`${DASHBOARD_BASE8}/directory/${encodeURIComponent(projectId)}/${encodeURIComponent(apiVersion)}`);
8379
8457
  }
8380
8458
  async function confirm(message, interactive2) {
8381
8459
  if (!interactive2) return false;
@@ -13571,6 +13649,13 @@ async function projectSpecText(caller, proj2) {
13571
13649
  }
13572
13650
  }
13573
13651
  var DOMAIN_RULE = "DOMAIN_CUSTOM_REQUIRED";
13652
+ function hostnameProblem(raw) {
13653
+ const v = (raw ?? "").trim().toLowerCase();
13654
+ if (!v) return null;
13655
+ if (/^https?:\/\/|\//.test(v)) return "Just the hostname, no https:// and no path: mcp.yourdomain.com";
13656
+ if (!v.includes(".")) return `"${v}" is only part of it. Type the whole hostname: ${v}.yourdomain.com`;
13657
+ return /^([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,}$/.test(v) ? null : "That is not a valid hostname: mcp.yourdomain.com";
13658
+ }
13574
13659
  async function ensureMcpDomain(caller, proj2, r, opts, defaults, interactive2, say) {
13575
13660
  const domainRule = r.report.results.find((x) => x.id === DOMAIN_RULE);
13576
13661
  if (!domainRule || domainRule.pass) return { active: true };
@@ -13587,14 +13672,36 @@ async function ensureMcpDomain(caller, proj2, r, opts, defaults, interactive2, s
13587
13672
  hostname2 = suggested;
13588
13673
  } else {
13589
13674
  const { default: inquirer3 } = await import("inquirer");
13590
- const { value } = await inquirer3.prompt([{ type: "input", name: "value", message: "Hostname for the MCP server (on a domain you control):", ...suggested ? { default: suggested } : {} }]);
13591
- hostname2 = value?.trim().toLowerCase();
13675
+ for (; ; ) {
13676
+ const { value } = await inquirer3.prompt([{ type: "input", name: "value", message: "Full hostname for the MCP server, on a domain you control (mcp.yourdomain.com; Enter to skip for now):", ...suggested ? { default: suggested } : {}, validate: (v) => hostnameProblem(v) ?? true }]);
13677
+ hostname2 = value?.trim().toLowerCase();
13678
+ if (!hostname2) {
13679
+ say(import_chalk32.default.dim(" Skipped: no MCP hostname yet. It stays open on the scorecard."));
13680
+ return { active: false };
13681
+ }
13682
+ const sp2 = (0, import_ora12.default)(`Registering ${hostname2}\u2026`).start();
13683
+ try {
13684
+ row = await producer(caller, { method: "POST", path: base2, body: { hostname: hostname2, api_version: proj2.apiVersion, kind: "mcp" }, summary: `Add custom MCP domain ${hostname2}` });
13685
+ sp2.succeed(`Registered ${row.hostname ?? hostname2} as the MCP server's domain.`);
13686
+ break;
13687
+ } catch (e) {
13688
+ if (!isApiError(e, 400) && !isApiError(e, 409)) {
13689
+ sp2.stop();
13690
+ throw e;
13691
+ }
13692
+ sp2.fail(`${hostname2} was refused: ${e instanceof Error ? e.message : String(e)}`);
13693
+ }
13694
+ }
13592
13695
  }
13593
13696
  }
13594
13697
  if (!hostname2) throw new Error("A custom MCP hostname is required for the directories (--domain mcp.<your domain>).");
13595
- const sp2 = opts.json ? null : (0, import_ora12.default)(`Registering ${hostname2}\u2026`).start();
13596
- row = await producer(caller, { method: "POST", path: base2, body: { hostname: hostname2, api_version: proj2.apiVersion, kind: "mcp" }, summary: `Add custom MCP domain ${hostname2}` });
13597
- sp2?.succeed(`Registered ${row.hostname ?? hostname2} as the MCP server's domain.`);
13698
+ if (!row) {
13699
+ const problem = hostnameProblem(hostname2);
13700
+ if (problem) throw new Error(`${problem} (--domain mcp.<your domain>)`);
13701
+ const sp2 = opts.json ? null : (0, import_ora12.default)(`Registering ${hostname2}\u2026`).start();
13702
+ row = await producer(caller, { method: "POST", path: base2, body: { hostname: hostname2, api_version: proj2.apiVersion, kind: "mcp" }, summary: `Add custom MCP domain ${hostname2}` });
13703
+ sp2?.succeed(`Registered ${row.hostname ?? hostname2} as the MCP server's domain.`);
13704
+ }
13598
13705
  }
13599
13706
  const hostname = row.hostname ?? opts.domain ?? "";
13600
13707
  if (row.status === "active") return { active: true, hostname, id: row.id };
@@ -13689,11 +13796,10 @@ async function ensureHostedUpstream(caller, teamId, proj2, r, opts, interactive2
13689
13796
  let chosen = header;
13690
13797
  let open = !!opts.upstreamOpen;
13691
13798
  if (!chosen && !open && interactive2) {
13692
- if (shape(cur).hosts?.upstreamKind === "public" && !shape(cur).hosts?.upstreamHeader) say(import_chalk32.default.dim(" An open backend can be called around sign-in and the per-user rules; a shared secret header closes that."));
13693
13799
  const { default: inquirer3 } = await import("inquirer");
13694
- const { name } = await inquirer3.prompt([{ type: "input", name: "name", message: `Does the backend need a header to accept calls from ${B.product}, such as an API key or a shared secret? (Header name; Enter = no, it checks its own credentials)` }]);
13800
+ const { name } = await inquirer3.prompt([{ type: "input", name: "name", message: "Secret header your backend checks? (e.g. X-Api-Key; Enter if none)" }]);
13695
13801
  if (name?.trim()) {
13696
- const { value } = await inquirer3.prompt([{ type: "password", name: "value", mask: "*", message: `Value of ${name.trim()} (stored encrypted, never shown again):`, validate: (v) => (v ?? "").length > 0 ? true : "A value is needed; Ctrl-C leaves." }]);
13802
+ const { value } = await inquirer3.prompt([{ type: "password", name: "value", mask: "*", message: `Its value (stored encrypted):`, validate: (v) => (v ?? "").length > 0 ? true : "A value is needed; Ctrl-C leaves." }]);
13697
13803
  chosen = { name: name.trim(), value };
13698
13804
  } else open = true;
13699
13805
  }
@@ -13806,8 +13912,6 @@ function installInterrupt(ctx) {
13806
13912
  return () => process.off("SIGINT", handler);
13807
13913
  }
13808
13914
  async function runConnector(target2, opts) {
13809
- const caller = requireCaller();
13810
- const { teamId } = await resolveActingTeam(caller, opts.team);
13811
13915
  const json = !!opts.json;
13812
13916
  const interactive2 = !!process.stdin.isTTY && !json && !opts.yes;
13813
13917
  const say = (s) => {
@@ -13823,8 +13927,9 @@ async function runConnector(target2, opts) {
13823
13927
  const created = await createFromSpec(specSource, opts, say);
13824
13928
  projectName = created.name;
13825
13929
  version2 = created.version;
13826
- say(import_chalk32.default.green(` ${B.thing} ${import_chalk32.default.bold(created.name)} created.`));
13827
13930
  }
13931
+ const caller = requireCaller();
13932
+ const { teamId } = await resolveActingTeam(caller, opts.team);
13828
13933
  const proj2 = await resolveProjectVia(caller, teamId, projectName, version2);
13829
13934
  const flagBody = directoryBodyFromFlags(opts);
13830
13935
  const onlyAttest = !!opts.attest?.length && !flagBody.directory_metadata && !opts.full && !specSource && !only && !opts.status;
@@ -13849,13 +13954,16 @@ async function runConnector(target2, opts) {
13849
13954
  const stored = r.progress?.platforms ?? null;
13850
13955
  const modeOn = r.target?.project?.directoryReady === true;
13851
13956
  if (opts.status) {
13957
+ const cc = claudeCodeTests(r, proj2);
13852
13958
  if (json) {
13853
- writeJsonLine({ ready: isReady(r, platforms), project: proj2.projectId, headline: r.rendered.headline, summary: r.progress?.summary ?? null, progress: r.progress ?? null, report: r.report });
13959
+ writeJsonLine({ ready: isReady(r, platforms), project: proj2.projectId, headline: r.rendered.headline, summary: r.progress?.summary ?? null, progress: r.progress ?? null, claude_code: cc, report: r.report });
13854
13960
  process.exit(isReady(r, platforms) ? 0 : 1);
13855
13961
  }
13856
13962
  const card2 = scorecardText(r);
13857
13963
  if (card2) console.log(card2);
13858
13964
  else printReport(r, false);
13965
+ if (cc) console.log(`
13966
+ ${claudeCodeText(cc)}`);
13859
13967
  process.exit(isReady(r, platforms) ? 0 : 1);
13860
13968
  }
13861
13969
  const dirsDiffer = explicit && !!stored && (stored.length !== platforms.length || stored.some((p) => !platforms.includes(p)));
@@ -13869,9 +13977,23 @@ async function runConnector(target2, opts) {
13869
13977
  r = await fetchDirectoryReport(caller, proj2, reportOpts());
13870
13978
  }
13871
13979
  const link = pageLinkOf(r, proj2);
13872
- const sum = summaryOf(r);
13873
- if (sum) {
13874
- say(`${import_chalk32.default.bold("Finish this here or at")} ${import_chalk32.default.cyan(link)} ${import_chalk32.default.dim("\xB7")} ${import_chalk32.default.bold(sum)}`);
13980
+ if (r.progress) {
13981
+ const pct = r.progress.readiness?.percent;
13982
+ const where = platforms.map((p) => p === "claude" ? "Claude" : "ChatGPT").join(" and ");
13983
+ const rule = import_chalk32.default.dim("\u2500".repeat(64));
13984
+ say("");
13985
+ say(rule);
13986
+ say(` ${import_chalk32.default.bold(`Get it listed in ${where}`)}${typeof pct === "number" ? import_chalk32.default.dim(` \xB7 ${pct}% ready`) : ""}`);
13987
+ if (caller.mode === "anon") {
13988
+ say(` A listing needs an account (your own MCP address is billed to it). Sign in, then carry on:`);
13989
+ say(` ${import_chalk32.default.cyan(`npx ${B.cli}@latest login`)} ${import_chalk32.default.dim(`it keeps ${proj2.projectId}, moving it into your team`)}`);
13990
+ say(` ${import_chalk32.default.cyan(`npx ${B.cli}@latest connector ${proj2.projectId}`)} ${import_chalk32.default.dim("or finish on the page it links")}`);
13991
+ say(rule);
13992
+ return;
13993
+ }
13994
+ say(` ${import_chalk32.default.bold("Here:")} answer the questions below. Stop any time; every answer is kept.`);
13995
+ say(` ${import_chalk32.default.bold("Or in your browser:")} ${import_chalk32.default.cyan(link)}`);
13996
+ say(rule);
13875
13997
  const done = doneLine(r);
13876
13998
  if (done) say(import_chalk32.default.dim(` ${done}`));
13877
13999
  } else {
@@ -14162,7 +14284,7 @@ async function waitForExternal(caller, proj2, r, platforms, domain, opts, intera
14162
14284
  say(" One sign-in from a real Claude client is the last proof. In another terminal:");
14163
14285
  say(` claude mcp add --transport http ${alias} ${url}`);
14164
14286
  say(` claude then /mcp \u2192 ${alias} \u2192 sign in with the reviewer account`);
14165
- say(import_chalk32.default.dim(` or in claude.ai: Settings \u2192 Connectors \u2192 Add custom connector \u2192 ${url} (what a reviewer does).`));
14287
+ say(import_chalk32.default.dim(` or in claude.ai: Customize > Connectors \u2192 Add custom connector \u2192 ${url} (what a reviewer does).`));
14166
14288
  say(import_chalk32.default.dim(` Afterwards: claude mcp remove ${alias}`));
14167
14289
  const { default: inquirer3 } = await import("inquirer");
14168
14290
  const { go } = await inquirer3.prompt([{ type: "confirm", name: "go", message: "Sign in from Claude now, then press Enter to check (n skips; the step stays open):", default: true }]);
@@ -14583,7 +14705,7 @@ function printSubmitSteps(r, mdPath, platforms = PLATFORMS, watching = false) {
14583
14705
  const open = isOpenMode(r);
14584
14706
  const claude = platforms.includes("claude");
14585
14707
  const chatgpt = platforms.includes("chatgpt");
14586
- const where = [claude ? "Claude (Settings \u2192 Connectors)" : null, chatgpt ? "ChatGPT (chatgpt.com/plugins \u2192 Add custom MCP server)" : null].filter(Boolean).join(" and in ");
14708
+ const where = [claude ? "Claude (Customize > Connectors)" : null, chatgpt ? "ChatGPT (chatgpt.com/plugins \u2192 Add custom MCP server)" : null].filter(Boolean).join(" and in ");
14587
14709
  const steps = [
14588
14710
  `Test it yourself first: add ${import_chalk32.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.`,
14589
14711
  ...claude ? [`Claude: ${import_chalk32.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_chalk32.default.cyan(import_path7.default.basename(mdPath))}.`] : [],
@@ -14920,6 +15042,42 @@ function backendPort(f2) {
14920
15042
 
14921
15043
  // src/commands/skill.ts
14922
15044
  init_api();
15045
+
15046
+ // src/lib/listing-rules.ts
15047
+ var LISTING_RULES = `**Who reads what.** The listing (name, tagline, description, example prompts, categories) is read by an ordinary
15048
+ person browsing the directory, deciding whether to add the App. They do not know or care what an API, an endpoint, a spec, a gateway,
15049
+ a sandbox or hosting is. Use cases and reviewer instructions are read by the directory's review team,
15050
+ who add the App and try it.
15051
+
15052
+ **Read the input the right way.** The API's own title and description were written by developers for developers: hosting,
15053
+ sandboxes, limits, setup, git, internals. Never copy or paraphrase that; use it only to understand the domain. Learn what
15054
+ the App does from its tools: each tool is something a person can ask for.
15055
+
15056
+ **The fields.**
15057
+ - Name (\`--server-name\`): what a person would call it, often the product or brand. No "API", "MCP", "Plugin", "Server",
15058
+ version numbers or the word "App". At most 100 characters, 30 when ChatGPT is a target.
15059
+ - Tagline (\`--tagline\`): one plain sentence saying what the person gets done; starts with a verb; at most 200 characters.
15060
+ - Description (\`--description\`): 300 to 1,500 characters, two or three short paragraphs. What a person can do, in their
15061
+ terms ("find a table for four on Friday", not "createReservation endpoint"); who it is for; one sentence on whether it
15062
+ only reads or can also change things in their account. No lists of tool names, no marketing adjectives.
15063
+ - Example prompts (\`--prompt\`, three to five): what a person would really type, each answered by a tool, under 128
15064
+ characters, varied (look something up, then act on it), concrete values, no placeholders.
15065
+ - Prerequisites (\`connector answers --prerequisites\`): one sentence, what someone needs before using it.
15066
+ - Categories (\`connector answers --category\`, one to five): plain words a person would browse by.
15067
+ - Use cases (\`connector answers --use-cases\`): 200 to 1,500 characters for the review team: the real jobs, in the
15068
+ person's words, each tied to what comes back.
15069
+ - Reviewer instructions (\`connector answers --reviewer-instructions\`): 400 to 2,500 characters, numbered: where to add
15070
+ it (the MCP address), how to sign in (the test account; never write a password), then three to six prompts to try,
15071
+ each with what the reviewer should see. Build on what the last test run showed; never invent a result.
15072
+
15073
+ **Never write** claims nothing supports (secure, real-time, compliant, best, official, fastest), the names Claude,
15074
+ ChatGPT, Anthropic or OpenAI (except in reviewer instructions, to say where to add it), URLs other than the MCP address in
15075
+ reviewer instructions, prices, or anything about legal, health or finance use.
15076
+
15077
+ **Never answer for the user**: the privacy policy URL, the icon, whose API it is (data handling), health data, sponsored
15078
+ content, high-risk areas, and the attestations. Ask; these are promises only they can make.`;
15079
+
15080
+ // src/commands/skill.ts
14923
15081
  var SKILL_NAME = "ship-safely";
14924
15082
  var SKILL_RELATIVE_PATH = import_path10.default.join(".claude", "skills", SKILL_NAME, "SKILL.md");
14925
15083
  function skillNameFor(brand = BRAND) {
@@ -15184,10 +15342,10 @@ Most people new to it do not know the words tenant, OpenAPI or header, so plain
15184
15342
  1. **"Want me to make an MCP server from this API?"** \u2014 the API is deployed: \`${run2} create --target <its OpenAPI spec or URL>\`
15185
15343
  (sign-in and per-user rules come with it; it prints the MCP address). The app runs on this machine:
15186
15344
  \`${run2} dev --port <port>\`, as its own detached process. The next step to offer for this folder: ${question}
15187
- 2. **"Want it listed in Claude or ChatGPT?"** \u2014 \`${run2} connector <name>\` makes it a Claude connector
15188
- (\`--compliant chatgpt\` a ChatGPT plugin, \`--compliant all\` both). It prints the scorecard first \u2014 N of 12 done
15189
- and what is left \u2014 then runs every open step in order and saves each answer as it is given; the user can stop any time
15190
- and finish on the page it links. \`${run2} connector <name> --status\` is the scorecard alone.
15345
+ 2. **"Want it listed in Claude or ChatGPT?"** \u2014 right after the MCP server exists, \`${run2} connector <name>\`
15346
+ (\`--compliant chatgpt\` for ChatGPT, \`--compliant all\` both). Its first line is the page link: give it to the user
15347
+ first, then go through the open steps with them, writing the listing yourself (\`${run2} skills connector\` has the
15348
+ steps and the writing rules). \`${run2} connector <name> --status\` says what is left, with a Claude Code test line per use case.
15191
15349
 
15192
15350
  \`${run2} demo\` shows a sample app with an MCP server in front of it in 30 seconds, no login (it prints the links; it opens nothing).
15193
15351
 
@@ -15260,13 +15418,11 @@ function shipMcpMarkdown() {
15260
15418
  who can override: the dashboard, Tenant \u2192 Admins.
15261
15419
  7. **Try it from Claude Code.** \`claude mcp add --transport http <name> <the MCP address it printed>\`, then \`/mcp\` and
15262
15420
  sign in; call one tool. What a reviewer does, before a reviewer does it.
15263
- 8. **List it.** \`${run2} connector <name>\` (Claude; \`--compliant chatgpt\` or \`all\` for OpenAI's directory): the scorecard
15264
- first, then every open step in order \u2014 the MCP hostname (one CNAME the user adds at their DNS provider; relay the exact
15265
- record), the listing answers pre-filled from the spec, the reviewer account, the proof run \u2014 and the submission packet when
15266
- every check passes. It needs the hosted app, not a tunnel: have the production URL and any header the backend needs ready, and
15267
- run the proof again after switching. Stop any time; \`${run2} connector <name> --status\` says what is left and links the page where the rest
15268
- can be finished.
15269
- The user submits the packet to Anthropic or OpenAI themselves.
15421
+ 8. **List it.** \`${run2} connector <name>\` (Claude; \`--compliant chatgpt\` or \`all\` for OpenAI's directory). Give the user
15422
+ the page link it prints first, then follow \`${run2} skills connector\`: the MCP address (one CNAME; relay the exact record),
15423
+ sign-in and the test account, the listing and the review-team answers that you write by its rules, the tests (one
15424
+ \`claude -p\` per use case, from \`--status\`), and the packet. It needs the hosted app, not a tunnel. Stop any time;
15425
+ \`${run2} connector <name> --status\` says what is left. The user submits the packet to Anthropic or OpenAI themselves.
15270
15426
 
15271
15427
  Report what you did in plain words: the MCP address, who can sign in, what is locked, what is left on the scorecard.
15272
15428
 
@@ -15676,6 +15832,41 @@ Each tenant (a group of users with their own sign-in) has a developer portal whe
15676
15832
 
15677
15833
  Tenant admins (they can see and manage every record and every user): the dashboard, Tenant \u2192 Admins, by email, even
15678
15834
  before that person ever signs in. The whole APIblaze command set is here too: \`npx ${cli} help --all\`.`);
15835
+ case "connector":
15836
+ return page("Get it listed in Claude (and ChatGPT) with MCPblaze", `
15837
+ The listing is the MCP server, sent to Anthropic's directory (Claude) and, if the user wants, OpenAI's (ChatGPT).
15838
+ The user submits. Say it "meets the requirements MCPblaze can verify; listing decisions are made by Anthropic and OpenAI",
15839
+ never "certified".
15840
+
15841
+ **Start, and give the page first.** \`npx ${cli} connector <name>\` (Claude; add \`--compliant chatgpt\` or \`--compliant all\`).
15842
+ Its first line is "Finish this here or at <page link> \xB7 N left": give the user that link before anything else. The page
15843
+ has the same steps with forms, a % ready ring, and a Testing tab; the user can work there, here, or both. Every answer is
15844
+ saved as it is given; \`npx ${cli} connector <name> --status\` says what is left at any time.
15845
+
15846
+ **The steps, in the page's order.** Go through the open ones with the user, one at a time, in plain words:
15847
+ 1. **Get listed**: the command turns it on. Nothing is public until the user submits.
15848
+ 2. **Connect your API**: the MCP address on their own domain (one CNAME at their DNS provider; relay the exact record
15849
+ it prints) and the hosted app (\`--target-url\`, \`--upstream-header\`): a tunnel cannot be listed.
15850
+ 3. **How people sign in to your MCP**: sign-in by default, a test account is made for the reviewers. Public, read-only
15851
+ data can skip sign-in (\`--auth-mode open\`).
15852
+ 4. **What your App can do**: the tools (\`npx ${cli} skills mcp\` has the method).
15853
+ 5. **Your listing, and the questions for the review team**: YOU write the text, from the code and the tools, following
15854
+ the rules below. Show the user every field, then save what they approve:
15855
+ \`npx ${cli} connector <name> --step listing -y --server-name "\u2026" --tagline "\u2026" --description "\u2026" --prompt "\u2026" --prompt "\u2026" --prompt "\u2026"\`
15856
+ (plus \`--support\`, \`--vuln-contact\`; for ChatGPT \`--short-description\`, \`--developer-name\`, \`--website-url\`, \`--terms-url\`)
15857
+ and \`npx ${cli} connector answers <name> --use-cases "\u2026" --prerequisites "\u2026" --category "\u2026" --reviewer-instructions "\u2026"\`.
15858
+ 6. **Test**: \`npx ${cli} connector exercise <name>\` calls every tool as the reviewer. Then
15859
+ \`npx ${cli} connector <name> --status\` prints the Claude Code lines: one \`claude mcp add\`, one sign-in, and one
15860
+ \`claude -p '\u2026' --allowedTools 'mcp__<name>'\` per example prompt, each with what should come back. Run each, tell the user
15861
+ what came back, and fix a tool's description (\`npx ${cli} mcp ops set\`) when a prompt did not reach the right tool.
15862
+ 7. **Send**: when every check passes it writes the packet. The user submits it on claude.ai/directory/manage (and the
15863
+ OpenAI portal for ChatGPT).
15864
+
15865
+ ## Writing the listing
15866
+
15867
+ ${LISTING_RULES}
15868
+
15869
+ ${HEDGE}`);
15679
15870
  case "rules":
15680
15871
  return page("Who can change what (MCPblaze)", `
15681
15872
  - With a spec, \`dev\` and \`create\` lock every record to the person who created it: an agent acting for Alice
@@ -23513,7 +23704,8 @@ function buildProgram() {
23513
23704
  const connector = program2.command("connector").description(descriptionFor({
23514
23705
  apiblaze: `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)`,
23515
23706
  mcpblaze: "Make it a Claude connector (--compliant chatgpt: a ChatGPT plugin), directory-ready in one command: the scorecard first, then every open step in order \u2014 the MCP hostname (one CNAME), the listing answers pre-filled from the spec, the reviewer account, the proof run \u2014 and the submission packet when every check passes. Stop any time; `--status` shows what is left and the page to finish on. You submit. They decide."
23516
- })).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("--target-url <url>", "Where the app runs in production (https): a listing needs the hosted app, not a tunnel; asked when missing").option("--upstream-header <header>", '"Name: value", the header the backend needs to accept these calls, stored encrypted; asked when missing').option("--upstream-header-env <name=var>", "The same with the value read from an environment variable, so it stays out of your shell history").option("--upstream-open", "The backend checks its own credentials: no header").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("--exercise", "Run every tool now even when the scorecard says step 8 is done (asked only while it is open)").option("--no-exercise", 'Skip the "Run every tool now?" step').option("--step <id>", "Run one step of the scorecard and exit: mode \xB7 address \xB7 access \xB7 tools \xB7 listing \xB7 answers \xB7 attestations \xB7 prove \xB7 signin \xB7 token \xB7 packet").option("--status", "Print the scorecard (what is done, what is left, where) and exit: 0 when ready, 1 while something is left").option("--verbose", "The full rule report at the end instead of the scorecard (also: check --directory-ready)").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) => {
23707
+ })).argument("[project-or-spec]", `The ${B.thingShort} (project name), or an OpenAPI URL/file to create a directory-ready ${B.thingShort} from first`).option("--target <url|file>", "Same as giving the OpenAPI URL or file as the argument (the spelling `create` uses)").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("--target-url <url>", "Where the app runs in production (https): a listing needs the hosted app, not a tunnel; asked when missing").option("--upstream-header <header>", '"Name: value", the header the backend needs to accept these calls, stored encrypted; asked when missing').option("--upstream-header-env <name=var>", "The same with the value read from an environment variable, so it stays out of your shell history").option("--upstream-open", "The backend checks its own credentials: no header").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("--exercise", "Run every tool now even when the scorecard says step 8 is done (asked only while it is open)").option("--no-exercise", 'Skip the "Run every tool now?" step').option("--step <id>", "Run one step of the scorecard and exit: mode \xB7 address \xB7 access \xB7 tools \xB7 listing \xB7 answers \xB7 attestations \xB7 prove \xB7 signin \xB7 token \xB7 packet").option("--status", "Print the scorecard (what is done, what is left, where) and exit: 0 when ready, 1 while something is left").option("--verbose", "The full rule report at the end instead of the scorecard (also: check --directory-ready)").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 (arg, opts) => {
23708
+ const project = arg ?? opts.target;
23517
23709
  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>`);
23518
23710
  await runConnector(project, opts);
23519
23711
  }));
@@ -23866,14 +24058,21 @@ Error: ${err.message}`));
23866
24058
  function hintStaleCacheOnUnknownOption(cmd) {
23867
24059
  cmd.exitOverride((err) => {
23868
24060
  if (err.code === "commander.unknownOption") {
23869
- console.error(import_chalk78.default.yellow(
23870
- `
23871
- This is ${B.cli} v${version}. If that option is documented but this build
23872
- does not have it, your npx cache is serving an older copy \u2014 re-run as:
24061
+ const help = `${B.cli} ${cmd === program2 ? "" : `${cmd.name()} `}--help`;
24062
+ if (B.cli === "mcpblaze") {
24063
+ console.error(import_chalk78.default.yellow(`
24064
+ This is ${B.cli} v${version}. \`npx ${help}\` lists what this command takes.
24065
+ `));
24066
+ } else {
24067
+ console.error(import_chalk78.default.yellow(
24068
+ `
24069
+ This is ${B.cli} v${version}. \`npx ${help}\` lists what this command takes. If the option is documented
24070
+ but this build does not have it, your npx cache is serving an older copy \u2014 re-run as:
23873
24071
  `
23874
- ));
23875
- console.error(import_chalk78.default.cyan(` npx ${B.cli}@latest ${process.argv.slice(2).join(" ")}
24072
+ ));
24073
+ console.error(import_chalk78.default.cyan(` npx ${B.cli}@latest ${process.argv.slice(2).join(" ")}
23876
24074
  `));
24075
+ }
23877
24076
  }
23878
24077
  process.exit(err.exitCode);
23879
24078
  });
@@ -23883,9 +24082,27 @@ does not have it, your npx cache is serving an older copy \u2014 re-run as:
23883
24082
  return program2;
23884
24083
  }
23885
24084
 
24085
+ // src/lib/bare-create.ts
24086
+ var SPEC = /^(https?:\/\/\S+|\S+\.(ya?ml|json))$/i;
24087
+ function bareCreateArgv(args, commands) {
24088
+ if (!args.length) return args;
24089
+ for (let i = 0; i < args.length; i += 1) {
24090
+ const a = args[i];
24091
+ if (a.startsWith("-")) continue;
24092
+ const prev = args[i - 1];
24093
+ const isValue = !!prev && prev.startsWith("--") && !prev.includes("=");
24094
+ if (!isValue && commands.has(a)) return args;
24095
+ }
24096
+ if (SPEC.test(args[0])) return ["create", "--target", ...args];
24097
+ if (args.some((a) => a === "--target" || a.startsWith("--target="))) return ["create", ...args];
24098
+ return args;
24099
+ }
24100
+
23886
24101
  // src/index.ts
23887
24102
  installBrandFilter();
23888
24103
  var program = buildProgram();
24104
+ var known = new Set(program.commands.flatMap((c) => [c.name(), ...c.aliases()]).concat(["help"]));
24105
+ process.argv = [...process.argv.slice(0, 2), ...bareCreateArgv(process.argv.slice(2), known)];
23889
24106
  process.on("exit", () => renderTrace());
23890
24107
  installFetchGuard(version);
23891
24108
  var firstCommand = process.argv.slice(2).find((a) => !a.startsWith("-"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apiblaze",
3
- "version": "0.21.73",
3
+ "version": "0.21.75",
4
4
  "description": "APIblaze CLI — Chat with your APIs, Manage your API keys, users and groups with the APIblaze serverless proxy",
5
5
  "keywords": [
6
6
  "apiblaze",