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.
- package/dist/index.js +90 -75
- 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.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
|
|
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 <
|
|
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
|
-
|
|
9652
|
-
- **A step the rest depends on** (the tunnel, creating the proxy, its key):
|
|
9653
|
-
|
|
9654
|
-
(\`! <command>\` in Claude Code).
|
|
9655
|
-
- **An optional step** (groups, admins, widgets, a rule, limits):
|
|
9656
|
-
|
|
9657
|
-
-
|
|
9658
|
-
|
|
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
|
|
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
|
|
9684
|
-
|
|
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 ?? "
|
|
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 = [
|
|
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(
|
|
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
|
|
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
|
-
|
|
9700
|
-
steps.push(`
|
|
9701
|
-
steps.push(`
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
9721
|
+
`;
|
|
9722
|
+
return `${frontMatter}# ${P}
|
|
9714
9723
|
|
|
9715
|
-
|
|
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
|
-
##
|
|
9727
|
+
## Older copies of this guide are installed
|
|
9718
9728
|
|
|
9719
9729
|
${stale.map((f) => `- \`${f}\``).join("\n")}
|
|
9720
9730
|
|
|
9721
|
-
They
|
|
9722
|
-
|
|
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
|
-
##
|
|
9735
|
+
## What ${P} is
|
|
9726
9736
|
|
|
9727
|
-
|
|
9737
|
+
${pitchText()}
|
|
9728
9738
|
|
|
9729
|
-
"
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
9736
|
-
|
|
9737
|
-
|
|
9738
|
-
|
|
9739
|
-
|
|
9740
|
-
|
|
9741
|
-
|
|
9742
|
-
|
|
9743
|
-
|
|
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
|
-
|
|
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
|
|
9766
|
+
## Their own app, after the demo
|
|
9752
9767
|
|
|
9753
|
-
|
|
9754
|
-
|
|
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
|
|
9771
|
+
## One thing at a time
|
|
9757
9772
|
|
|
9758
|
-
|
|
9759
|
-
|
|
9760
|
-
|
|
9761
|
-
their
|
|
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
|
|
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.
|