apiblaze 0.21.40 → 0.21.42

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 +90 -75
  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.40";
37
+ version = "0.21.42";
38
38
  }
39
39
  });
40
40
 
@@ -394,12 +394,13 @@ __export(api_exports, {
394
394
  });
395
395
  function countRun(opts = {}) {
396
396
  if (/^(1|true|yes)$/i.test((process.env.DO_NOT_TRACK ?? "").trim())) return;
397
+ if (opts.event !== void 0 && !/^[a-z0-9-]{1,48}$/.test(opts.event)) return;
397
398
  try {
398
399
  const child = (0, import_child_process.spawn)(process.execPath, ["-e", COUNT_RUN_CHILD], {
399
400
  detached: true,
400
401
  stdio: "ignore",
401
402
  windowsHide: true,
402
- env: { ...process.env, ABZ_COUNT_URL: opts.url ?? `${PUBLIC_API_BASE}/cli/hello`, ABZ_COUNT_HEADERS: JSON.stringify(cliHeaders()) }
403
+ env: { ...process.env, ABZ_COUNT_URL: (opts.url ?? `${PUBLIC_API_BASE}/cli/hello`) + (opts.event ? `/${opts.event}` : ""), ABZ_COUNT_HEADERS: JSON.stringify(cliHeaders()) }
403
404
  });
404
405
  child.on("error", () => {
405
406
  });
@@ -7720,7 +7721,7 @@ async function portFree(port) {
7720
7721
  }
7721
7722
  async function freePort(from, avoid = []) {
7722
7723
  for (let p = from; p < from + 200; p++) if (!avoid.includes(p) && await portFree(p)) return p;
7723
- throw new DemoError("No free port for the demo app.", "Close another local server, then run again.");
7724
+ throw new DemoError("No free port for the demo app.", "Close another local server, then run again.", "no-free-port");
7724
7725
  }
7725
7726
  function readRegistry() {
7726
7727
  try {
@@ -7901,34 +7902,34 @@ async function createProxy2(name, backendPort2, openapi, tenantName) {
7901
7902
  return await createProxyAnonymous(body);
7902
7903
  } catch (err) {
7903
7904
  const status2 = err.status;
7904
- if (status2 === 429) throw new DemoError("Too many demos were started from this network in the last hour.", `Wait a few minutes, then run: ${LATEST} demo`);
7905
- if (status2 && status2 >= 500) throw new DemoError(`${B.product} is having trouble right now.`, `Try again in a minute: ${LATEST} demo`);
7906
- throw new DemoError(`Could not create the demo (${err.message}).`, `Run again: ${LATEST} demo`);
7905
+ if (status2 === 429) throw new DemoError("Too many demos were started from this network in the last hour.", `Wait a few minutes, then run: ${LATEST} demo`, "create-429");
7906
+ if (status2 && status2 >= 500) throw new DemoError(`${B.product} is having trouble right now.`, `Try again in a minute: ${LATEST} demo`, "create-5xx");
7907
+ throw new DemoError(`Could not create the demo (${err.message}).`, `Run again: ${LATEST} demo`, "create-failed");
7907
7908
  }
7908
7909
  }
7909
7910
  async function waitTunnel(dir) {
7910
7911
  for (let i = 0; i < 200; i++) {
7911
7912
  const st = loadState(dir);
7912
- if (st?.error) throw new DemoError(st.error, `Run again: ${LATEST} demo`);
7913
+ if (st?.error) throw new DemoError(st.error, `Run again: ${LATEST} demo`, "tunnel-error");
7913
7914
  if (st?.ready) return;
7914
7915
  await new Promise((r) => setTimeout(r, 100));
7915
7916
  }
7916
- throw new DemoError("The tunnel did not open in time.", `Try: ${LATEST} demo restart`);
7917
+ throw new DemoError("The tunnel did not open in time.", `Try: ${LATEST} demo restart`, "tunnel-timeout");
7917
7918
  }
7918
7919
  async function waitThrough(apiUrl, serverKey) {
7919
7920
  let last = 0;
7920
- for (let i = 0; i < 60; i++) {
7921
+ for (let i = 0; i < 120; i++) {
7921
7922
  try {
7922
7923
  const r = await fetch(`${apiUrl}/reservations`, { headers: { "x-api-key": serverKey, "x-end-user-id": "ana" } });
7923
7924
  last = r.status;
7924
7925
  if (r.status === 200) return;
7925
- if (r.status === 402) throw new DemoError(`${B.product} refused the demo's free calls. That is a problem on our side, not yours.`, `Try again in a few minutes: ${LATEST} demo`);
7926
+ if (r.status === 402) throw new DemoError(`${B.product} refused the demo's free calls. That is a problem on our side, not yours.`, `Try again in a few minutes: ${LATEST} demo`, "trial-402");
7926
7927
  } catch (e) {
7927
7928
  if (e instanceof DemoError) throw e;
7928
7929
  }
7929
7930
  await new Promise((r) => setTimeout(r, 1e3));
7930
7931
  }
7931
- throw new DemoError(`The demo is running but ${B.product} cannot reach it yet (last answer ${last}).`, `Run again: ${LATEST} demo`);
7932
+ throw new DemoError(`The demo is running but ${B.product} cannot reach it yet (last answer ${last}).`, `Run again: ${LATEST} demo`, `cannot-reach-${last}`);
7932
7933
  }
7933
7934
  async function confirmStep(opts, question) {
7934
7935
  if (opts.yes || !interactive()) return true;
@@ -8003,9 +8004,10 @@ function routesOf(openapi) {
8003
8004
  async function runDemo(opts = {}) {
8004
8005
  if (opts.serve) return serveTunnel(import_path5.default.resolve(opts.serve));
8005
8006
  try {
8006
- await startDemo(opts);
8007
+ countRun({ event: await startDemo(opts) });
8007
8008
  } catch (err) {
8008
8009
  const e = err instanceof DemoError ? err : new DemoError(`The demo could not start (${err.message}).`, `Run again: ${LATEST} demo`);
8010
+ countRun({ event: `demo-failed-${e.why}` });
8009
8011
  await stopDemos().catch(() => 0);
8010
8012
  console.log(`${import_chalk24.default.red("\u2717")} ${e.message}`);
8011
8013
  console.log(` ${e.fix}`);
@@ -8061,9 +8063,10 @@ async function startDemo(opts) {
8061
8063
  const cdDir = rel === "." ? "" : `cd ${rel} && `;
8062
8064
  const shell = (c, note = "") => console.log(` ${import_chalk24.default.dim("$")} ${import_chalk24.default.cyan(c)}${note ? ` ${import_chalk24.default.dim(note)}` : ""}`);
8063
8065
  const ok = (text, note = "") => console.log(` ${import_chalk24.default.green("\u2713")} ${text}${note ? ` ${import_chalk24.default.dim(note)}` : ""}`);
8064
- const stopHere = (lines) => {
8066
+ const stopHere = (step, lines) => {
8065
8067
  console.log();
8066
8068
  for (const l of lines) console.log(` ${l}`);
8069
+ return `demo-declined-${step}`;
8067
8070
  };
8068
8071
  titleCard(["Restaurant Reservation Service Demo", `ResiResi, with ${B.product} in front`]);
8069
8072
  await stopDemos();
@@ -8090,16 +8093,16 @@ paths:`));
8090
8093
  for (const r of routesOf(openapi)) console.log(` ${r.method.padEnd(7)}${r.path.padEnd(21)}${import_chalk24.default.dim(r.summary)}`);
8091
8094
  console.log(import_chalk24.default.dim(" No login, no API keys, no permission checks in its code. It only checks one shared backend secret (in .env.local)."));
8092
8095
  if (!await confirmStep(opts, `Ok to serve the backend on localhost:${backendPort2}?`)) {
8093
- return stopHere([
8096
+ return stopHere("backend", [
8094
8097
  `Stopped before starting anything. ResiResi is written to ${rel}.`,
8095
8098
  `Start it any time: ${cdDir}npm run start ${import_chalk24.default.dim("\xB7")} or run the whole demo again: ${LATEST} demo`
8096
8099
  ]);
8097
8100
  }
8098
8101
  shell(`${cdDir}npm run start`, "npm run stop \xB7 npm run restart");
8099
- if (!appCtl(dir, "--background")) throw new DemoError("The ResiResi app could not start.", `Run again: ${LATEST} demo`);
8102
+ if (!appCtl(dir, "--background")) throw new DemoError("The ResiResi app could not start.", `Run again: ${LATEST} demo`, "app-start");
8100
8103
  registerDemo(dir);
8101
8104
  const appUp = await busy("Starting the reservation backend and its front-end\u2026", () => waitPorts([backendPort2, directPort]), 1800);
8102
- if (!appUp) throw new DemoError(`The ResiResi app did not start (see ${import_path5.default.join(rel, STATE_DIR, "app.log")}).`, `Run again: ${LATEST} demo`);
8105
+ if (!appUp) throw new DemoError(`The ResiResi app did not start (see ${import_path5.default.join(rel, STATE_DIR, "app.log")}).`, `Run again: ${LATEST} demo`, "app-not-up");
8103
8106
  ok(`${import_chalk24.default.bold("Reservation backend")}: localhost:${backendPort2}`, "unprotected");
8104
8107
  ok(`${import_chalk24.default.bold("Unprotected reservation front-end")}, going directly to the reservation backend: ${import_chalk24.default.bold(`http://localhost:${directPort}`)}`);
8105
8108
  console.log(" Book a table directly, the way the unprotected front-end does:");
@@ -8108,7 +8111,7 @@ paths:`));
8108
8111
  console.log(import_chalk24.default.cyan(` -H "x-abz-user-id: ana" -H "x-abz-tenant-id: ${ninoTenant}" \\`));
8109
8112
  console.log(import_chalk24.default.cyan(` -H "content-type: application/json" -d '{"time":"19:30","guests":2}'`));
8110
8113
  if (!await confirmStep(opts, `Ok to put an ${B.product} proxy and MCP in front of the reservation backend?`)) {
8111
- return stopHere([
8114
+ return stopHere("proxy", [
8112
8115
  `Stopped before ${B.product}. ResiResi keeps running on this laptop: http://localhost:${directPort} ${import_chalk24.default.dim(`(${cdDir}npm run stop stops it)`)}`,
8113
8116
  `Run the whole demo again any time: ${LATEST} demo`
8114
8117
  ]);
@@ -8127,7 +8130,7 @@ paths:`));
8127
8130
  const rr = r;
8128
8131
  const tenant3 = rr.tenant ?? tenantFromPortal(rr.portal ?? rr.devPortal) ?? "";
8129
8132
  const missing = [!cpKey2 && "workspace key", !teamId2 && "team", !serverKey2 && "API key", !tenant3 && "tenant"].filter(Boolean);
8130
- if (missing.length) throw new DemoError(`${B.product} did not return everything the demo needs (${missing.join(", ")}).`, `Run again: ${LATEST} demo`);
8133
+ if (missing.length) throw new DemoError(`${B.product} did not return everything the demo needs (${missing.join(", ")}).`, `Run again: ${LATEST} demo`, "setup-incomplete");
8131
8134
  const createSecs2 = ((Date.now() - tCreate) / 1e3).toFixed(1);
8132
8135
  adoptWorkspace(cpKey2, teamId2, rr.claim_code);
8133
8136
  const cp = (p, init) => cpFetch(cpKey2, p, init).then(() => true).catch(() => false);
@@ -8149,7 +8152,7 @@ paths:`));
8149
8152
  return { limited: lim, secretOk: sec };
8150
8153
  })()
8151
8154
  ]);
8152
- if (!secretOk) throw new DemoError(`Could not give ${B.product} the backend secret.`, `Run again: ${LATEST} demo`);
8155
+ if (!secretOk) throw new DemoError(`Could not give ${B.product} the backend secret.`, `Run again: ${LATEST} demo`, "secret-failed");
8153
8156
  return { cpKey: cpKey2, teamId: teamId2, serverKey: serverKey2, project: project2, version: version3, tenant: tenant3, gino: gino2, createSecs: createSecs2, adminOk: adminOk2, ginaOk: ginaOk2, limited: limited2, claimCode: rr.claim_code, applied: rr.secure?.applied ?? [] };
8154
8157
  });
8155
8158
  const { cpKey, teamId, serverKey, project, version: version2, tenant: tenant2, gino, createSecs, adminOk, ginaOk, limited } = setup;
@@ -8201,7 +8204,7 @@ paths:`));
8201
8204
  }
8202
8205
  ]);
8203
8206
  if (!await confirmStep(opts, `Ok to create a tunnel allowing ${project} to forward requests to localhost:${backendPort2}?`)) {
8204
- return stopHere([
8207
+ return stopHere("tunnel", [
8205
8208
  `Stopped before the tunnel. The proxy exists but cannot reach this laptop yet.`,
8206
8209
  `Open the tunnel any time: ${LATEST} demo start`
8207
8210
  ]);
@@ -8216,7 +8219,7 @@ paths:`));
8216
8219
  ok(`${import_chalk24.default.bold("Tunnel")}: the public address reaches localhost:${backendPort2} on this laptop`, `checked with a real call \xB7 ${Math.round((Date.now() - t0) / 1e3)} s so far`);
8217
8220
  printAddresses(dir);
8218
8221
  if (!await confirmStep(opts, "Ok to launch the protected website, which will use the proxy to reach the backend?")) {
8219
- return stopHere([
8222
+ return stopHere("website", [
8220
8223
  `Stopped before the protected website. The API and MCP addresses above work now.`,
8221
8224
  `Start the website any time: ${cdDir}npm run restart`
8222
8225
  ]);
@@ -8229,12 +8232,13 @@ paths:`));
8229
8232
  appCtl(dir, "--background");
8230
8233
  return waitPorts([appPort, directPort, backendPort2]);
8231
8234
  }, 1500);
8232
- if (!siteUp) throw new DemoError(`The protected website did not start (see ${import_path5.default.join(rel, STATE_DIR, "app.log")}).`, `Try: ${cdDir}npm run restart`);
8235
+ if (!siteUp) throw new DemoError(`The protected website did not start (see ${import_path5.default.join(rel, STATE_DIR, "app.log")}).`, `Try: ${cdDir}npm run restart`, "site-not-up");
8233
8236
  ok(`${import_chalk24.default.bold("Protected reservation website")}, going through the ${B.product} proxy ${apiUrl}: ${import_chalk24.default.bold(`http://localhost:${appPort}`)}`);
8234
8237
  ok(`${import_chalk24.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");
8235
8238
  console.log();
8236
8239
  console.log(import_chalk24.default.dim(` Logs: ${logsCommand(readEnvFile(import_path5.default.join(dir, ".env.local")))} \xB7 Stop the tunnel: ${LATEST} demo stop \xB7 Your own app: ${LATEST} dev --port <port>`));
8237
8240
  await offerBrowserAndLogs(dir, opts);
8241
+ return "demo-ready";
8238
8242
  }
8239
8243
  function printAddresses(dir) {
8240
8244
  const env = readEnvFile(import_path5.default.join(dir, ".env.local"));
@@ -8266,10 +8270,12 @@ async function runDemoStart(opts = {}) {
8266
8270
  await spawnTunnel(dir, state);
8267
8271
  } catch (err) {
8268
8272
  const e = err instanceof DemoError ? err : new DemoError(`The tunnel could not start (${err.message}).`, `Start over: ${LATEST} demo`);
8273
+ countRun({ event: `demo-restart-failed-${e.why}` });
8269
8274
  console.log(`${import_chalk24.default.red("\u2717")} ${e.message}`);
8270
8275
  console.log(` ${e.fix}`);
8271
8276
  process.exit(1);
8272
8277
  }
8278
+ countRun({ event: "demo-restarted" });
8273
8279
  console.log(`${import_chalk24.default.green("\u2713")} Tunnel open: the public address reaches localhost:${env.BACKEND_PORT} again ${import_chalk24.default.dim(`(${Math.round((Date.now() - t0) / 1e3)} s)`)}`);
8274
8280
  printAddresses(dir);
8275
8281
  const appUp = await fetch(`http://127.0.0.1:${env.APP_PORT}/api/config`).then(() => true).catch(() => false);
@@ -8373,9 +8379,10 @@ var init_demo = __esm({
8373
8379
  DEMO_GINO_ADMIN_EMAIL = "gina@ginopizza.example";
8374
8380
  RULE_SENTENCE = "Only the person who made a reservation can change or cancel it";
8375
8381
  DemoError = class extends Error {
8376
- constructor(message, fix) {
8382
+ constructor(message, fix, why = "unexpected") {
8377
8383
  super(message);
8378
8384
  this.fix = fix;
8385
+ this.why = why;
8379
8386
  }
8380
8387
  };
8381
8388
  ADJ = ["brave", "calm", "clever", "eager", "happy", "jolly", "lucky", "merry", "noble", "quick", "sunny", "swift", "witty", "zesty", "bright", "gentle"];
@@ -9648,14 +9655,15 @@ function folderReport(f, opts = {}) {
9648
9655
  function blockedMarkdown() {
9649
9656
  return `## If a command is blocked or fails
9650
9657
 
9651
- A permission prompt declined, a command denied, or a step needing access you do not have:
9652
- - **A step the rest depends on** (the tunnel, creating the proxy, its key): stop there. In one
9653
- line, say what it was for and give the user the exact command to run themselves
9654
- (\`! <command>\` in Claude Code). Then wait. If they would rather not, say it stops here.
9655
- - **An optional step** (groups, admins, widgets, a rule, limits): skip it. Say in one line what
9656
- is missing because of it, and carry on with what does not need it.
9657
- - **Always:** never retry it in pieces, take another route, or write code to replace it. Never
9658
- say something works until you have checked it: a call that answered, or a page you loaded.
9658
+ When a permission prompt is declined, a command is denied, or a step needs access you do not have:
9659
+ - **A step the rest depends on** (the tunnel, creating the proxy, its key): nothing after it can
9660
+ work, so the useful move is to stop there, say what it was for, and give the user the exact
9661
+ command to run themselves (\`! <command>\` in Claude Code).
9662
+ - **An optional step** (groups, admins, widgets, a rule, limits): the rest works without it. Skip
9663
+ it and say what is missing because of it.
9664
+ - Working around a blocked step (retrying it in pieces, another route, code written to replace
9665
+ it) leaves a half-built setup that fails in ways nobody can read. And something only works
9666
+ once it has been checked: a call that answered, or a page that loaded.
9659
9667
  `;
9660
9668
  }
9661
9669
  function staleSkillCopies(cwd = process.cwd(), home = import_os2.default.homedir()) {
@@ -9675,90 +9683,97 @@ function heroMarkdown(found, stale = []) {
9675
9683
  const run2 = `npx ${B.cli}@latest`;
9676
9684
  const report = found ? folderReport(found) : null;
9677
9685
  const port = found ? backendPort(found) : void 0;
9678
- const folder = report ? `## This folder, checked by the CLI just now: read no files to decide what to offer
9686
+ const folder = report ? `## This folder (the CLI checked it just now)
9679
9687
 
9680
9688
  ${report.lines.map((l) => `- ${l}`).join("\n")}
9681
9689
  ` : `## First, the folder
9682
9690
 
9683
- Run \`${run2} skills\` in the project folder and follow what it prints: it checks the folder
9684
- and gives the exact question to ask.
9691
+ Run \`${run2} skills\` in the project folder: it checks the folder and prints the current version
9692
+ of this guide, with the next step that fits it.
9685
9693
  `;
9686
- const question = report?.question ?? "<the question it prints>";
9694
+ const question = report?.question ?? "the question it prints for the folder";
9687
9695
  const ownApi = report?.ownApi && found ? (() => {
9688
9696
  const specArg = found.specFile ? ` --openapi ${found.specFile}` : "";
9689
- const steps = [`Say one line: "Putting your API behind ${P}, about 30 seconds."`];
9697
+ const steps = [];
9690
9698
  if (!found.listening) {
9691
9699
  steps.push(`Start it first as its own detached process${found.start ? `: \`nohup ${found.start} > app.log 2>&1 < /dev/null &\`` : ", with its usual start command"}. Wait until localhost:${port ?? "<port>"} answers.`);
9692
9700
  }
9693
9701
  if (!found.specFile && !found.specUrl) {
9694
- steps.push('Write `./openapi.yaml` from its route files first: every path and method, and the id each POST returns. Say "Writing an OpenAPI spec from your code."');
9702
+ steps.push("Write `./openapi.yaml` from its route files first: every path and method, and the id each POST returns.");
9695
9703
  }
9696
9704
  const spec2 = found.specFile ? specArg : !found.specUrl ? " --openapi ./openapi.yaml" : "";
9697
- steps.push(`Open the tunnel as its own detached process, never \`cmd &\` inside a shell call that ends:
9705
+ steps.push(`Open the tunnel as its own detached process (a \`cmd &\` inside a shell call that ends is killed with it):
9698
9706
  \`nohup ${run2} dev --port ${port ?? "<port>"}${spec2} --auto > ${B.cli}-dev.log 2>&1 < /dev/null &\`
9699
- Then read ${B.cli}-dev.log until it says "is live on the internet" (about 20 seconds). It saves the key to .env.local itself.`);
9700
- steps.push(`Tell them in plain words, with the two addresses it printed: their API is on the internet at the API address, and AI assistants can use it at the MCP address. API keys, sign-in, rules for who can change what, and rate limits are already on. Say "saved your key to .env.local". Leave the tunnel running.`);
9701
- steps.push(`Ask one question: **"Want it ready for real customers: their own API keys, sign-in, and only-the-owner-can-change rules?"** If yes, run \`${run2} skills ship\` and follow it from "Adopt".`);
9707
+ ${B.cli}-dev.log says "is live on the internet" after about 20 seconds. It saves the key to .env.local itself.`);
9708
+ steps.push(`What they have then, with the two addresses it printed: their API on the internet at the API address, and an address AI assistants can use (MCP). API keys, sign-in, rules for who can change what, and rate limits are already on. The key is saved to .env.local and never printed. The tunnel keeps running.`);
9709
+ steps.push(`The natural next step: ready for real customers, with their own API keys, sign-in, and only-the-owner-can-change rules. \`${run2} skills ship\` has that procedure, from "Adopt".`);
9702
9710
  return `
9703
- ## Their API: they say yes to putting it behind ${P}
9711
+ ## Their API, if they want it behind ${P}
9704
9712
 
9705
9713
  ${steps.map((t, i) => `${i + 1}. ${t}`).join("\n")}
9706
9714
  `;
9707
9715
  })() : "";
9708
- return `---
9716
+ const frontMatter = found ? "" : `---
9709
9717
  name: ${SKILL_NAME}
9710
9718
  description: Use when the user asks what ${P} (or ${B.cli}) does or can do for them; says yes to a demo or asks to see / try / demo it; wants to ship, share or expose an API; wants API keys, sign-in, per-user data, rate limits or quotas; wants AI agents to use their app safely; or says users can reach each other's records.
9711
9719
  ---
9712
9720
 
9713
- # ${P}
9721
+ `;
9722
+ return `${frontMatter}# ${P}
9714
9723
 
9715
- This output is current: it replaces any copy of this skill loaded earlier.
9724
+ What \`${run2} skills\` reports: what ${P} is, what is in this folder, and the next steps you can
9725
+ offer. Nothing here needs to run before the user says so.
9716
9726
  ${stale.length ? `
9717
- ## Outdated copies are installed: ignore them
9727
+ ## Older copies of this guide are installed
9718
9728
 
9719
9729
  ${stale.map((f) => `- \`${f}\``).join("\n")}
9720
9730
 
9721
- They hold an older walkthrough. Follow only what this output says. Tell the user once that the
9722
- file is outdated (re-run \`${run2} skills --install --force\` in that folder to refresh it).
9731
+ They were written for an older CLI; this output is the current one. The user can refresh a copy
9732
+ with \`${run2} skills --install --force\` in its folder.
9723
9733
  ` : ""}
9724
9734
  ${folder}
9725
- ## Explain: they ask what ${P} does or can do for them
9735
+ ## What ${P} is
9726
9736
 
9727
- Run nothing else. Say exactly this and nothing else: no list of use cases, no commands, no options. Then stop:
9737
+ ${pitchText()}
9728
9738
 
9729
- "${pitchText()} ${question}"
9739
+ For a first "what does it do", that is usually enough: the demo shows the rest. The next step to
9740
+ offer: ${question}
9730
9741
 
9731
- Do not use the words proxy, tenant, OpenAPI or header unless the user does first.
9742
+ Most people new to it do not know the words proxy, tenant, OpenAPI or header, so plain words work best.
9732
9743
  ${ownApi}
9733
- ## Demo: they say yes to the sample app, or ask to see or try it
9744
+ ## The demo, if they want to see it
9745
+
9746
+ \`${run2} demo\` takes about 30 seconds, needs no login and asks nothing. It:
9747
+ - writes a small table-booking app into ./${B.cli}-demo and runs it on this machine (localhost, from
9748
+ port 3000 up, skipping any port another program uses);
9749
+ - puts a temporary ${P} gateway in front of it (anonymous, no account) with a tunnel to the app;
9750
+ - prints the links once the page is live, then exits. It opens nothing and never prints a key
9751
+ (keys are saved in ./${B.cli}-demo/.env.local).
9734
9752
 
9735
- 1. Say one line: "Starting the demo, about 30 seconds."
9736
- 2. Run exactly this, in the foreground. It needs no login and asks nothing. It sets
9737
- everything up and exits by itself once the page is live. It opens nothing: give the user the App link it prints:
9738
- \`${run2} demo\`
9739
- 3. When it prints \u2713, tell this story in plain words, with the App link it printed:
9740
- - "This booking app runs on your laptop. ${P} just gave it a public address, and one for AI assistants."
9741
- - "On the Reservations tab, ask the assistant to book a table, and it appears in your list. The switch at the top shows the same app straight to localhost, unprotected."
9742
- - "The Admin tab is what each pizzeria's admin gets: their own API keys and people, kept apart."
9743
- Then ask one question: **"Want to put your own app on the internet like this?"**
9744
- 4. If it prints \u2717, say its one sentence and run the one command it gives, once.
9753
+ Run it in the foreground. It is self-contained: nothing in its folder needs reading or changing,
9754
+ and its own output says what happened. When it prints \u2713, the links it printed are the thing to
9755
+ show, in plain words:
9756
+ - the booking app runs on their laptop, and ${P} just gave it a public address, plus one for AI
9757
+ assistants;
9758
+ - on the Reservations tab, asking the assistant to book a table makes it appear in the list; the
9759
+ switch at the top shows the same app straight to localhost, unprotected;
9760
+ - the Admin tab is what each pizzeria's admin gets: their own API keys and people, kept apart.
9761
+ The question it leads to: **"Want to put your own app on the internet like this?"**
9745
9762
 
9746
- Rules for the demo: keep each update to one line. Do not narrate steps, read the demo's
9747
- files, open logs, run other commands, or write code. Never show a key: the demo saves it
9748
- in \`.env.local\` and never prints it.
9763
+ If it prints \u2717, its one sentence says what failed and the one command after it is the fix.
9749
9764
 
9750
9765
  ${blockedMarkdown()}
9751
- ## Their own app: they say yes after the demo
9766
+ ## Their own app, after the demo
9752
9767
 
9753
- Their app runs on this machine \u2192 it will use \`${run2} dev\`; it is deployed \u2192 \`${run2} create\`.
9754
- Run \`${run2} skills ship\` and follow what it prints.
9768
+ On this machine \u2192 \`${run2} dev\`; deployed \u2192 \`${run2} create\`. \`${run2} skills ship\` has the
9769
+ whole procedure.
9755
9770
 
9756
- ## One thing at a time, later
9771
+ ## One thing at a time
9757
9772
 
9758
- When they ask about one thing, read only its page: \`${run2} skills keys\` (API keys) \xB7
9759
- \`signin\` (OAuth sign-in and the developer portal) \xB7 \`rules\` (who can change what) \xB7
9760
- \`limits\` (rate limits and quotas) \xB7 \`mcp\` (AI agents) \xB7 \`widgets\` (keys, users and chat on
9761
- their site) \xB7 \`ship\` (the whole procedure for their own app).
9773
+ Each topic has its own page: \`${run2} skills keys\` (API keys) \xB7 \`signin\` (OAuth sign-in and the
9774
+ developer portal) \xB7 \`rules\` (who can change what) \xB7 \`limits\` (rate limits and quotas) \xB7 \`mcp\`
9775
+ (AI agents) \xB7 \`widgets\` (keys, users and chat on their site) \xB7 \`ship\` (the whole procedure for
9776
+ their own app).
9762
9777
  `;
9763
9778
  }
9764
9779
  function shipMarkdown() {
@@ -9923,7 +9938,7 @@ ONLY for what the scan could not find \u2014 one short list of questions, not an
9923
9938
  of the job: move the call to a server route, delete the public variable, say so in the report.
9924
9939
  - **Both** (monorepo): do the backend first, then the frontend.
9925
9940
 
9926
- **Ask for what is missing \u2014 and nothing else.**
9941
+ **Ask only for what is missing.**
9927
9942
  - Backend found only on localhost \u2192 "Where is it hosted for production?" (a URL, or "not yet").
9928
9943
  - Backend protected by a secret whose value is in an env var \u2192 "What is the value of
9929
9944
  \`<VAR>\`, or should I keep it where it is?" Never read secrets out of \`.env\` aloud.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apiblaze",
3
- "version": "0.21.40",
3
+ "version": "0.21.42",
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",