apiblaze 0.21.72 → 0.21.73
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 +1067 -921
- 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.73";
|
|
38
38
|
}
|
|
39
39
|
});
|
|
40
40
|
|
|
@@ -424,7 +424,10 @@ function recordCall(e) {
|
|
|
424
424
|
}
|
|
425
425
|
function maskBody(body) {
|
|
426
426
|
if (body === void 0) return void 0;
|
|
427
|
-
return JSON.stringify(body, (k, v)
|
|
427
|
+
return JSON.stringify(body, function(k, v) {
|
|
428
|
+
if (typeof v !== "string") return v;
|
|
429
|
+
return SECRET_KEY.test(k) || k === "value" && this?.value_secret === true ? "***" : v;
|
|
430
|
+
});
|
|
428
431
|
}
|
|
429
432
|
function renderTrace() {
|
|
430
433
|
if (!verbose || entries.length === 0) return;
|
|
@@ -7227,6 +7230,261 @@ var init_create = __esm({
|
|
|
7227
7230
|
}
|
|
7228
7231
|
});
|
|
7229
7232
|
|
|
7233
|
+
// src/commands/config.ts
|
|
7234
|
+
var config_exports = {};
|
|
7235
|
+
__export(config_exports, {
|
|
7236
|
+
environmentsWithTarget: () => environmentsWithTarget,
|
|
7237
|
+
runRename: () => runRename,
|
|
7238
|
+
runTargetSet: () => runTargetSet
|
|
7239
|
+
});
|
|
7240
|
+
async function patchConfig(project, opts, body, summary) {
|
|
7241
|
+
const caller = requireCaller();
|
|
7242
|
+
const { teamId } = await resolveActingTeam(caller, opts.team);
|
|
7243
|
+
const proj2 = await resolveProjectVia(caller, teamId, project, opts.apiversion);
|
|
7244
|
+
const spinner = (0, import_ora9.default)(summary + "...").start();
|
|
7245
|
+
try {
|
|
7246
|
+
const out = await producer(caller, {
|
|
7247
|
+
method: "PATCH",
|
|
7248
|
+
path: `/projects/${proj2.projectId}/${proj2.apiVersion}`,
|
|
7249
|
+
body,
|
|
7250
|
+
summary
|
|
7251
|
+
});
|
|
7252
|
+
spinner.succeed(`${summary} \u2014 ${proj2.projectName} v${proj2.apiVersion}.`);
|
|
7253
|
+
if (opts.json) console.log(JSON.stringify(out ?? { ok: true }));
|
|
7254
|
+
} catch (err) {
|
|
7255
|
+
spinner.fail(summary + " failed.");
|
|
7256
|
+
throw err;
|
|
7257
|
+
}
|
|
7258
|
+
}
|
|
7259
|
+
async function runTargetSet(project, opts) {
|
|
7260
|
+
if (!opts.url) {
|
|
7261
|
+
console.error(import_chalk24.default.red("--url is required."));
|
|
7262
|
+
process.exit(1);
|
|
7263
|
+
}
|
|
7264
|
+
if (!opts.env) {
|
|
7265
|
+
await patchConfig(project, opts, { target_url: opts.url }, `Set target \u2192 ${opts.url}`);
|
|
7266
|
+
return;
|
|
7267
|
+
}
|
|
7268
|
+
const caller = requireCaller();
|
|
7269
|
+
const { teamId } = await resolveActingTeam(caller, opts.team);
|
|
7270
|
+
const proj2 = await resolveProjectVia(caller, teamId, project, opts.apiversion);
|
|
7271
|
+
const environments = await environmentsWithTarget(caller, teamId, proj2, opts.env, opts.url);
|
|
7272
|
+
await patchConfig(project, opts, { environments }, `Set target for env ${opts.env} \u2192 ${opts.url}`);
|
|
7273
|
+
}
|
|
7274
|
+
async function environmentsWithTarget(caller, teamId, proj2, env, url) {
|
|
7275
|
+
const listing = await pageAll(
|
|
7276
|
+
(cursor) => producer(caller, {
|
|
7277
|
+
method: "GET",
|
|
7278
|
+
path: withPage(`/projects?team_id=${encodeURIComponent(teamId)}`, cursor),
|
|
7279
|
+
summary: `Read environments for ${proj2.projectName}`
|
|
7280
|
+
}),
|
|
7281
|
+
(b) => b?.projects
|
|
7282
|
+
);
|
|
7283
|
+
const row = listing.items.find((r) => r.project_id === proj2.projectId && r.api_version === proj2.apiVersion);
|
|
7284
|
+
const current = row?.config?.environments;
|
|
7285
|
+
const existing = current && typeof current === "object" && !Array.isArray(current) ? current : {};
|
|
7286
|
+
const prev = existing[env];
|
|
7287
|
+
return { ...existing, [env]: { ...prev && typeof prev === "object" ? prev : {}, target: url } };
|
|
7288
|
+
}
|
|
7289
|
+
async function runRename(project, opts) {
|
|
7290
|
+
if (!opts.displayName) {
|
|
7291
|
+
console.error(import_chalk24.default.red("--display-name is required."));
|
|
7292
|
+
process.exit(1);
|
|
7293
|
+
}
|
|
7294
|
+
await patchConfig(project, opts, { display_name: opts.displayName }, `Rename \u2192 "${opts.displayName}"`);
|
|
7295
|
+
}
|
|
7296
|
+
var import_chalk24, import_ora9;
|
|
7297
|
+
var init_config = __esm({
|
|
7298
|
+
"src/commands/config.ts"() {
|
|
7299
|
+
"use strict";
|
|
7300
|
+
import_chalk24 = __toESM(require("chalk"));
|
|
7301
|
+
import_ora9 = __toESM(require("ora"));
|
|
7302
|
+
init_caller();
|
|
7303
|
+
init_paging();
|
|
7304
|
+
}
|
|
7305
|
+
});
|
|
7306
|
+
|
|
7307
|
+
// src/commands/transform.ts
|
|
7308
|
+
var transform_exports = {};
|
|
7309
|
+
__export(transform_exports, {
|
|
7310
|
+
parseEntries: () => parseEntries,
|
|
7311
|
+
runTransformList: () => runTransformList,
|
|
7312
|
+
runTransformMap: () => runTransformMap,
|
|
7313
|
+
runTransformMapping: () => runTransformMapping,
|
|
7314
|
+
runTransformRemove: () => runTransformRemove,
|
|
7315
|
+
runTransformSetHeader: () => runTransformSetHeader,
|
|
7316
|
+
setUpstreamHeader: () => setUpstreamHeader
|
|
7317
|
+
});
|
|
7318
|
+
async function target(project, opts) {
|
|
7319
|
+
const caller = requireCaller();
|
|
7320
|
+
const { teamId } = await resolveActingTeam(caller, opts.team);
|
|
7321
|
+
const proj2 = await resolveProjectVia(caller, teamId, project, opts.apiversion);
|
|
7322
|
+
const base2 = `/projects/${encodeURIComponent(proj2.projectId)}/${encodeURIComponent(proj2.apiVersion)}/transforms`;
|
|
7323
|
+
return { caller, proj: proj2, base: base2 };
|
|
7324
|
+
}
|
|
7325
|
+
async function listRules(t) {
|
|
7326
|
+
const out = await producer(t.caller, {
|
|
7327
|
+
method: "GET",
|
|
7328
|
+
path: t.base,
|
|
7329
|
+
summary: `List transforms for ${t.proj.projectName}`
|
|
7330
|
+
});
|
|
7331
|
+
return out?.rules ?? [];
|
|
7332
|
+
}
|
|
7333
|
+
function describe(r) {
|
|
7334
|
+
const a = r.action ?? { type: "?" };
|
|
7335
|
+
if (a.type === "hardcode") {
|
|
7336
|
+
const secret = !a.value || /^abzsec:|^__ABZSEC_|^•+$/.test(a.value);
|
|
7337
|
+
return `set ${a.destination} = ${secret ? "(secret)" : a.value}`;
|
|
7338
|
+
}
|
|
7339
|
+
if (a.type === "remove") return `remove ${a.field}`;
|
|
7340
|
+
if (a.type === "map") return `map ${a.source} \u2192 ${a.destination}${a.lookup?.mapping_id ? " (through a mapping table)" : ""}`;
|
|
7341
|
+
return a.type;
|
|
7342
|
+
}
|
|
7343
|
+
async function runTransformList(project, opts) {
|
|
7344
|
+
const t = await target(project, opts);
|
|
7345
|
+
const rules = await listRules(t);
|
|
7346
|
+
if (opts.json) {
|
|
7347
|
+
console.log(JSON.stringify(rules, null, 2));
|
|
7348
|
+
return;
|
|
7349
|
+
}
|
|
7350
|
+
if (!rules.length) {
|
|
7351
|
+
console.log(import_chalk25.default.dim(`No transforms on ${t.proj.projectName} v${t.proj.apiVersion}.`));
|
|
7352
|
+
return;
|
|
7353
|
+
}
|
|
7354
|
+
for (const r of rules) {
|
|
7355
|
+
console.log(`${r.enabled ? import_chalk25.default.green("\u25CF") : import_chalk25.default.gray("\u25CB")} ${import_chalk25.default.bold(r.name)} ${import_chalk25.default.dim(`[${r.phase}] ${r.id}`)}`);
|
|
7356
|
+
console.log(` ${describe(r)}`);
|
|
7357
|
+
}
|
|
7358
|
+
}
|
|
7359
|
+
async function headerUpsert(t, header, value, secret) {
|
|
7360
|
+
const name = ruleNameFor(header);
|
|
7361
|
+
const action = {
|
|
7362
|
+
type: "hardcode",
|
|
7363
|
+
destination: `header:${header}`,
|
|
7364
|
+
value,
|
|
7365
|
+
// Encrypted at rest and masked on every read; main-proxy decrypts it only
|
|
7366
|
+
// on the way to the upstream.
|
|
7367
|
+
...secret ? { value_secret: true } : {}
|
|
7368
|
+
};
|
|
7369
|
+
const existing = (await listRules(t)).find((r) => r.name === name && r.phase === "request");
|
|
7370
|
+
const summary = `${existing ? "Update" : "Add"} transform: send ${header} to the upstream`;
|
|
7371
|
+
const send = () => producer(t.caller, existing ? { method: "PUT", path: `${t.base}/${encodeURIComponent(existing.id)}`, body: { action, enabled: true }, summary } : { method: "POST", path: t.base, body: { name, action, phase: "request", enabled: true }, summary });
|
|
7372
|
+
return { summary, send };
|
|
7373
|
+
}
|
|
7374
|
+
async function setUpstreamHeader(caller, proj2, header, value) {
|
|
7375
|
+
if (!HEADER_NAME.test(header)) throw new Error(`"${header}" is not a valid header name.`);
|
|
7376
|
+
if (!value) throw new Error(`The value for ${header} is empty.`);
|
|
7377
|
+
const base2 = `/projects/${encodeURIComponent(proj2.projectId)}/${encodeURIComponent(proj2.apiVersion)}/transforms`;
|
|
7378
|
+
await (await headerUpsert({ caller, proj: proj2, base: base2 }, header, value, true)).send();
|
|
7379
|
+
}
|
|
7380
|
+
async function runTransformSetHeader(project, header, opts) {
|
|
7381
|
+
if (!HEADER_NAME.test(header)) throw new Error(`"${header}" is not a valid header name.`);
|
|
7382
|
+
if (opts.value === void 0 === (opts.valueEnv === void 0)) {
|
|
7383
|
+
throw new Error("Pass exactly one of --value <value> or --value-env <VAR> (reads the value from that environment variable, so it stays out of your shell history).");
|
|
7384
|
+
}
|
|
7385
|
+
const value = opts.value ?? process.env[opts.valueEnv];
|
|
7386
|
+
if (!value) throw new Error(`Environment variable ${opts.valueEnv} is empty or not set.`);
|
|
7387
|
+
const t = await target(project, opts);
|
|
7388
|
+
const { summary, send } = await headerUpsert(t, header, value, !!opts.secret);
|
|
7389
|
+
const spinner = opts.json ? null : (0, import_ora10.default)(summary + "...").start();
|
|
7390
|
+
try {
|
|
7391
|
+
const out = await send();
|
|
7392
|
+
spinner?.succeed(`${summary} \u2014 ${t.proj.projectName} v${t.proj.apiVersion}${opts.secret ? " (stored encrypted)" : ""}.`);
|
|
7393
|
+
if (opts.json) console.log(JSON.stringify(out ?? { ok: true }));
|
|
7394
|
+
else console.log(import_chalk25.default.dim(` Every call the proxy forwards now carries ${header}. Your backend can reject calls without it.`));
|
|
7395
|
+
} catch (err) {
|
|
7396
|
+
spinner?.fail(summary + " failed.");
|
|
7397
|
+
throw err;
|
|
7398
|
+
}
|
|
7399
|
+
}
|
|
7400
|
+
async function runTransformRemove(project, nameOrId, opts) {
|
|
7401
|
+
const t = await target(project, opts);
|
|
7402
|
+
const rules = await listRules(t);
|
|
7403
|
+
const want = nameOrId.toLowerCase();
|
|
7404
|
+
const rule = rules.find((r) => r.id === nameOrId || r.name.toLowerCase() === want || r.name === ruleNameFor(nameOrId));
|
|
7405
|
+
if (!rule) {
|
|
7406
|
+
throw new Error(`No transform "${nameOrId}" on ${t.proj.projectName}.` + (rules.length ? ` Transforms: ${rules.map((r) => r.name).join(", ")}` : ""));
|
|
7407
|
+
}
|
|
7408
|
+
await producer(t.caller, {
|
|
7409
|
+
method: "DELETE",
|
|
7410
|
+
path: `${t.base}/${encodeURIComponent(rule.id)}`,
|
|
7411
|
+
summary: `Remove transform "${rule.name}"`
|
|
7412
|
+
});
|
|
7413
|
+
if (opts.json) console.log(JSON.stringify({ ok: true, removed: rule.id }));
|
|
7414
|
+
else console.log(`${import_chalk25.default.green("\u2713")} Removed transform ${import_chalk25.default.bold(rule.name)} from ${t.proj.projectName}.`);
|
|
7415
|
+
}
|
|
7416
|
+
function tablesBase(t) {
|
|
7417
|
+
return t.base.replace(/\/transforms$/, "/mappings");
|
|
7418
|
+
}
|
|
7419
|
+
async function listTables(t) {
|
|
7420
|
+
const out = await producer(t.caller, {
|
|
7421
|
+
method: "GET",
|
|
7422
|
+
path: tablesBase(t),
|
|
7423
|
+
summary: `List mapping tables for ${t.proj.projectName}`
|
|
7424
|
+
});
|
|
7425
|
+
return out?.tables ?? [];
|
|
7426
|
+
}
|
|
7427
|
+
function parseEntries(pairs) {
|
|
7428
|
+
return pairs.map((p) => {
|
|
7429
|
+
const i = p.indexOf("=");
|
|
7430
|
+
if (i <= 0) throw new Error(`"${p}" is not a from=to pair (e.g. FR=EUR).`);
|
|
7431
|
+
return { from: p.slice(0, i), to: p.slice(i + 1) };
|
|
7432
|
+
});
|
|
7433
|
+
}
|
|
7434
|
+
async function runTransformMapping(project, table, pairs, opts) {
|
|
7435
|
+
if (!pairs.length) throw new Error("Give at least one from=to pair, e.g. FR=EUR US=USD.");
|
|
7436
|
+
const entries2 = parseEntries(pairs);
|
|
7437
|
+
const t = await target(project, opts);
|
|
7438
|
+
const existing = (await listTables(t)).find((m) => m.name === table);
|
|
7439
|
+
const body = { name: table, entries: entries2, ...opts.secret ? { encrypt_values: true } : {} };
|
|
7440
|
+
const summary = `${existing ? "Replace" : "Create"} mapping table "${table}" (${entries2.length} entr${entries2.length === 1 ? "y" : "ies"})`;
|
|
7441
|
+
const out = await producer(t.caller, existing ? { method: "PUT", path: `${tablesBase(t)}/${encodeURIComponent(existing.id)}`, body, summary } : { method: "POST", path: tablesBase(t), body, summary });
|
|
7442
|
+
if (opts.json) {
|
|
7443
|
+
console.log(JSON.stringify(out ?? { ok: true }));
|
|
7444
|
+
return;
|
|
7445
|
+
}
|
|
7446
|
+
console.log(`${import_chalk25.default.green("\u2714")} ${summary} \u2014 ${t.proj.projectName} v${t.proj.apiVersion}.`);
|
|
7447
|
+
for (const e of entries2) console.log(import_chalk25.default.dim(` ${e.from} \u2192 ${opts.secret ? "(secret)" : e.to}`));
|
|
7448
|
+
}
|
|
7449
|
+
async function runTransformMap(project, source, destination, opts) {
|
|
7450
|
+
if (!FIELD_REF.test(source)) throw new Error(`Source "${source}" must look like header:X-Country, query:country or bodyvar:order.country.`);
|
|
7451
|
+
if (!/^(header|query|bodyvar):.+/.test(destination)) throw new Error(`Destination "${destination}" must look like header:X-Currency, query:currency or bodyvar:order.currency.`);
|
|
7452
|
+
if (!opts.table) throw new Error("--table <name> is required: the mapping table to look the value up in (create it with `transform mapping`).");
|
|
7453
|
+
const onNoMatch = opts.onNoMatch ?? "skip";
|
|
7454
|
+
if (!["skip", "fail", "passthrough"].includes(onNoMatch)) throw new Error("--on-no-match is skip, fail or passthrough.");
|
|
7455
|
+
const t = await target(project, opts);
|
|
7456
|
+
const table = (await listTables(t)).find((m) => m.name === opts.table);
|
|
7457
|
+
if (!table) throw new Error(`No mapping table "${opts.table}" on ${t.proj.projectName}. Create it: npx apiblaze transform mapping ${project} ${opts.table} FR=EUR \u2026`);
|
|
7458
|
+
const name = `map ${source} to ${destination}`;
|
|
7459
|
+
const action = {
|
|
7460
|
+
type: "map",
|
|
7461
|
+
source,
|
|
7462
|
+
destination,
|
|
7463
|
+
lookup: { mapping_id: table.id, on_no_match: onNoMatch }
|
|
7464
|
+
};
|
|
7465
|
+
const existing = (await listRules(t)).find((r) => r.name === name && r.phase === "request");
|
|
7466
|
+
const summary = `${existing ? "Update" : "Add"} transform: ${source} \u2192 ${destination} through "${table.name}"`;
|
|
7467
|
+
const out = await producer(t.caller, existing ? { method: "PUT", path: `${t.base}/${encodeURIComponent(existing.id)}`, body: { action, enabled: true }, summary } : { method: "POST", path: t.base, body: { name, action, phase: "request", enabled: true }, summary });
|
|
7468
|
+
if (opts.json) {
|
|
7469
|
+
console.log(JSON.stringify(out ?? { ok: true }));
|
|
7470
|
+
return;
|
|
7471
|
+
}
|
|
7472
|
+
console.log(`${import_chalk25.default.green("\u2714")} ${summary} \u2014 ${t.proj.projectName} v${t.proj.apiVersion}.`);
|
|
7473
|
+
console.log(import_chalk25.default.dim(` A value not in the table: ${onNoMatch === "skip" ? "nothing is written" : onNoMatch === "fail" ? "the call is refused" : "it is copied as-is"}.`));
|
|
7474
|
+
}
|
|
7475
|
+
var import_chalk25, import_ora10, HEADER_NAME, ruleNameFor, FIELD_REF;
|
|
7476
|
+
var init_transform = __esm({
|
|
7477
|
+
"src/commands/transform.ts"() {
|
|
7478
|
+
"use strict";
|
|
7479
|
+
import_chalk25 = __toESM(require("chalk"));
|
|
7480
|
+
import_ora10 = __toESM(require("ora"));
|
|
7481
|
+
init_caller();
|
|
7482
|
+
HEADER_NAME = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
|
|
7483
|
+
ruleNameFor = (header) => `set header ${header.toLowerCase()}`;
|
|
7484
|
+
FIELD_REF = /^(header|query|param|bodyvar|jwt):.+/;
|
|
7485
|
+
}
|
|
7486
|
+
});
|
|
7487
|
+
|
|
7230
7488
|
// src/lib/tools.ts
|
|
7231
7489
|
async function proj(teamId, name, version2) {
|
|
7232
7490
|
return resolveProject(teamId, name, version2);
|
|
@@ -7237,11 +7495,11 @@ function toolCatalogue() {
|
|
|
7237
7495
|
function findTool(name) {
|
|
7238
7496
|
return TOOLS.find((t) => t.name === name);
|
|
7239
7497
|
}
|
|
7240
|
-
var
|
|
7498
|
+
var import_chalk26, TOOLS;
|
|
7241
7499
|
var init_tools = __esm({
|
|
7242
7500
|
"src/lib/tools.ts"() {
|
|
7243
7501
|
"use strict";
|
|
7244
|
-
|
|
7502
|
+
import_chalk26 = __toESM(require("chalk"));
|
|
7245
7503
|
init_admin();
|
|
7246
7504
|
init_api();
|
|
7247
7505
|
init_resolve();
|
|
@@ -7270,15 +7528,15 @@ var init_tools = __esm({
|
|
|
7270
7528
|
const key = keys.dev ?? Object.values(keys)[0];
|
|
7271
7529
|
const url = `https://${a.name}.abz.run/${version2}/dev`;
|
|
7272
7530
|
const tryIt = buildTryItCurl(url, auth, key, res.tenant);
|
|
7273
|
-
const lines = [` ${
|
|
7274
|
-
if (res.devPortal) lines.push(` ${
|
|
7531
|
+
const lines = [` ${import_chalk26.default.dim("Proxy URL:")} ${import_chalk26.default.bold(url)}`];
|
|
7532
|
+
if (res.devPortal) lines.push(` ${import_chalk26.default.dim("Dev portal:")} ${res.devPortal}`);
|
|
7275
7533
|
const envs = Object.keys(keys);
|
|
7276
7534
|
if (envs.length) {
|
|
7277
|
-
lines.push("", ` ${
|
|
7535
|
+
lines.push("", ` ${import_chalk26.default.bold("API keys")} ${import_chalk26.default.dim("(bootstrapped \u2014 send as the X-API-Key header; shown once):")}`);
|
|
7278
7536
|
const w = Math.max(...envs.map((e) => e.length));
|
|
7279
|
-
for (const env of envs) lines.push(` ${
|
|
7537
|
+
for (const env of envs) lines.push(` ${import_chalk26.default.cyan(env.padEnd(w))} ${import_chalk26.default.green(keys[env])}`);
|
|
7280
7538
|
}
|
|
7281
|
-
if (tryIt) lines.push("", ` ${
|
|
7539
|
+
if (tryIt) lines.push("", ` ${import_chalk26.default.dim("Try it:")}`, ` ${import_chalk26.default.cyan(tryIt)}`);
|
|
7282
7540
|
return { ...res, proxy_url: url, keys, ...tryIt ? { try_it: tryIt } : {}, display: lines.join("\n") };
|
|
7283
7541
|
}
|
|
7284
7542
|
},
|
|
@@ -7446,14 +7704,14 @@ function truncate(value, max = 1500) {
|
|
|
7446
7704
|
}
|
|
7447
7705
|
function printCost(resp) {
|
|
7448
7706
|
const line = formatBilling(resp.billing);
|
|
7449
|
-
if (line) console.log(
|
|
7707
|
+
if (line) console.log(import_chalk27.default.magenta(` ${line}`) + import_chalk27.default.dim(` (${resp.llm.model}, ${resp.llm.total_tokens} tok)`));
|
|
7450
7708
|
}
|
|
7451
7709
|
async function runAgent(opts) {
|
|
7452
7710
|
requireAuth();
|
|
7453
7711
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
7454
7712
|
const { default: inquirer3 } = await import("inquirer");
|
|
7455
|
-
console.log(
|
|
7456
|
-
console.log(
|
|
7713
|
+
console.log(import_chalk27.default.bold("APIblaze agent") + import_chalk27.default.dim(` \xB7 team ${teamName ?? teamId}`));
|
|
7714
|
+
console.log(import_chalk27.default.dim('Ask me to create/delete/configure proxies, tenants, keys, domains, specs. Type "exit" to quit.\n'));
|
|
7457
7715
|
const history = [];
|
|
7458
7716
|
let queued = opts.first ?? (opts.seedFor ? `Seed realistic sample data through the proxy "${opts.seedFor}" so a directory reviewer sees a populated account: create a handful of records with each write tool, then list them back. Never mention or use credentials.` : null);
|
|
7459
7717
|
while (true) {
|
|
@@ -7461,16 +7719,16 @@ async function runAgent(opts) {
|
|
|
7461
7719
|
if (queued) {
|
|
7462
7720
|
text = queued;
|
|
7463
7721
|
queued = null;
|
|
7464
|
-
console.log(
|
|
7722
|
+
console.log(import_chalk27.default.cyan("you") + " \u203A " + text);
|
|
7465
7723
|
} else {
|
|
7466
|
-
const { input } = await inquirer3.prompt([{ type: "input", name: "input", message:
|
|
7724
|
+
const { input } = await inquirer3.prompt([{ type: "input", name: "input", message: import_chalk27.default.cyan("you") + " \u203A" }]);
|
|
7467
7725
|
text = (input ?? "").trim();
|
|
7468
7726
|
}
|
|
7469
7727
|
if (!text) continue;
|
|
7470
7728
|
if (["exit", "quit", ":q"].includes(text.toLowerCase())) break;
|
|
7471
7729
|
history.push({ role: "user", content: text });
|
|
7472
7730
|
for (let step = 0; step < MAX_TOOL_STEPS; step++) {
|
|
7473
|
-
const spinner = (0,
|
|
7731
|
+
const spinner = (0, import_ora11.default)({ text: "thinking...", color: "magenta" }).start();
|
|
7474
7732
|
let resp;
|
|
7475
7733
|
try {
|
|
7476
7734
|
resp = await callAgent(history, teamId);
|
|
@@ -7485,14 +7743,14 @@ async function runAgent(opts) {
|
|
|
7485
7743
|
}
|
|
7486
7744
|
history.push({ role: "assistant", content: resp.raw });
|
|
7487
7745
|
printCost(resp);
|
|
7488
|
-
if (resp.reply) console.log(
|
|
7746
|
+
if (resp.reply) console.log(import_chalk27.default.green("agent") + " \u203A " + resp.reply);
|
|
7489
7747
|
if (!resp.action) break;
|
|
7490
7748
|
const tool = findTool(resp.action.tool);
|
|
7491
7749
|
if (!tool) {
|
|
7492
7750
|
history.push({ role: "user", content: `TOOL_RESULT ${resp.action.tool}: error \u2014 unknown tool` });
|
|
7493
7751
|
continue;
|
|
7494
7752
|
}
|
|
7495
|
-
const runSpinner = (0,
|
|
7753
|
+
const runSpinner = (0, import_ora11.default)({ text: `running ${tool.name}...`, color: "cyan" }).start();
|
|
7496
7754
|
try {
|
|
7497
7755
|
const result = await tool.run(resp.action.args, { teamId });
|
|
7498
7756
|
runSpinner.succeed(`${tool.name} \u2713`);
|
|
@@ -7510,18 +7768,18 @@ async function runAgent(opts) {
|
|
|
7510
7768
|
}
|
|
7511
7769
|
renderTrace();
|
|
7512
7770
|
if (step === MAX_TOOL_STEPS - 1) {
|
|
7513
|
-
console.log(
|
|
7771
|
+
console.log(import_chalk27.default.dim(" (paused after several steps \u2014 tell me how to continue)"));
|
|
7514
7772
|
}
|
|
7515
7773
|
}
|
|
7516
7774
|
}
|
|
7517
|
-
console.log(
|
|
7775
|
+
console.log(import_chalk27.default.dim("\nBye."));
|
|
7518
7776
|
}
|
|
7519
|
-
var
|
|
7777
|
+
var import_chalk27, import_ora11, DASHBOARD_BASE7, MAX_TOOL_STEPS;
|
|
7520
7778
|
var init_agent = __esm({
|
|
7521
7779
|
"src/commands/agent.ts"() {
|
|
7522
7780
|
"use strict";
|
|
7523
|
-
|
|
7524
|
-
|
|
7781
|
+
import_chalk27 = __toESM(require("chalk"));
|
|
7782
|
+
import_ora11 = __toESM(require("ora"));
|
|
7525
7783
|
init_auth();
|
|
7526
7784
|
init_admin();
|
|
7527
7785
|
init_resolve();
|
|
@@ -8157,10 +8415,10 @@ async function runConnectorExercise(project, opts = {}) {
|
|
|
8157
8415
|
if (!json) console.log(s);
|
|
8158
8416
|
};
|
|
8159
8417
|
const deps = opts.deps ?? {};
|
|
8160
|
-
if (opts.report !== false) say(
|
|
8418
|
+
if (opts.report !== false) say(import_chalk28.default.cyan(` Report: ${reportLink(proj2.projectId, proj2.apiVersion)}`));
|
|
8161
8419
|
if (opts.emptyOk?.length) {
|
|
8162
8420
|
await producer(caller, { method: "PATCH", path: projectPath(proj2), body: { directory_metadata: { empty_ok: opts.emptyOk } }, summary: "Mark tools legitimately empty" });
|
|
8163
|
-
say(
|
|
8421
|
+
say(import_chalk28.default.dim(` marked legitimately empty: ${opts.emptyOk.join(", ")}`));
|
|
8164
8422
|
}
|
|
8165
8423
|
let r = await fetchDirectoryReport(caller, proj2, { probe: false });
|
|
8166
8424
|
const target2 = r.target ?? {};
|
|
@@ -8195,9 +8453,9 @@ async function runConnectorExercise(project, opts = {}) {
|
|
|
8195
8453
|
bearer = login.accessToken;
|
|
8196
8454
|
asMeta = login.as;
|
|
8197
8455
|
reviewerUsername = acct.username;
|
|
8198
|
-
say(
|
|
8456
|
+
say(import_chalk28.default.dim(` signed in as the reviewer (${acct.username}); the token expires in 1 h`));
|
|
8199
8457
|
} else {
|
|
8200
|
-
say(
|
|
8458
|
+
say(import_chalk28.default.dim(" open mode: no sign-in; this run's calls count as yours"));
|
|
8201
8459
|
}
|
|
8202
8460
|
const ua = `apiblaze-cli/${version} directory-exercise`;
|
|
8203
8461
|
const headers = { [RUN_HEADER]: runId2 };
|
|
@@ -8217,14 +8475,14 @@ async function runConnectorExercise(project, opts = {}) {
|
|
|
8217
8475
|
if (!go) {
|
|
8218
8476
|
row.note = "not exercised: producer declined";
|
|
8219
8477
|
notes.push({ tool: t.name, note: "not exercised: producer declined" });
|
|
8220
|
-
say(` ${
|
|
8478
|
+
say(` ${import_chalk28.default.yellow("\u2013")} ${t.name} not exercised (${interactive2 ? "declined" : "non-interactive: writes need a y"})`);
|
|
8221
8479
|
continue;
|
|
8222
8480
|
}
|
|
8223
8481
|
}
|
|
8224
8482
|
let v = localOutcome(await call3(t.name, args));
|
|
8225
8483
|
if (role === "read" && v.outcome !== "ok" && interactive2 && Object.keys(args).length) {
|
|
8226
8484
|
const { default: inquirer3 } = await import("inquirer");
|
|
8227
|
-
say(` ${mark(v.outcome)} ${t.name} refused ${JSON.stringify(args)}: ${
|
|
8485
|
+
say(` ${mark(v.outcome)} ${t.name} refused ${JSON.stringify(args)}: ${import_chalk28.default.dim(redactHead(v.text, 120) || v.outcome)}`);
|
|
8228
8486
|
const fixed = {};
|
|
8229
8487
|
for (const [name, cur] of Object.entries(args)) {
|
|
8230
8488
|
const p = t.inputProperties?.[name] ?? {};
|
|
@@ -8263,11 +8521,11 @@ async function runConnectorExercise(project, opts = {}) {
|
|
|
8263
8521
|
row.invalidText = redactHead(`${bad.broke} \u2192 ${e.text}`);
|
|
8264
8522
|
}
|
|
8265
8523
|
}
|
|
8266
|
-
say(` ${mark(v.outcome)} ${t.name} ${
|
|
8524
|
+
say(` ${mark(v.outcome)} ${t.name} ${import_chalk28.default.dim(v.text.replace(/\s+/g, " ").slice(0, 80))}${row.invalid !== "skipped" ? import_chalk28.default.dim(` \xB7 bad arg: ${row.invalid === "error" ? "refused with a message" : row.invalid}`) : ""}`);
|
|
8267
8525
|
}
|
|
8268
8526
|
if (Object.keys(learned).length) {
|
|
8269
8527
|
await producer(caller, { method: "PATCH", path: projectPath(proj2), body: { directory_metadata: { tool_examples: learned } }, summary: "Remember the working arguments" });
|
|
8270
|
-
say(
|
|
8528
|
+
say(import_chalk28.default.green(` \u2713 remembered the working arguments for ${Object.keys(learned).join(", ")} (used on every run from now on)`));
|
|
8271
8529
|
}
|
|
8272
8530
|
const creators = tools.filter((t) => toolRole(t) === "create");
|
|
8273
8531
|
if (!open && !opts.promptsOnly && opts.seed !== false && emptyReads.length && creators.length) {
|
|
@@ -8275,21 +8533,21 @@ async function runConnectorExercise(project, opts = {}) {
|
|
|
8275
8533
|
let go = opts.seed === true;
|
|
8276
8534
|
if (!go && interactive2) go = await confirm(`${emptyReads.length} read${emptyReads.length > 1 ? "s" : ""} returned nothing for the reviewer (${names}). Create a few records the reviewer will see, through ${creators.map((c) => c.name).join(", ")}? They are kept, not deleted.`, interactive2);
|
|
8277
8535
|
if (!go) {
|
|
8278
|
-
say(
|
|
8536
|
+
say(import_chalk28.default.dim(` seed skipped${interactive2 ? "" : " (non-interactive: pass --seed to create records for the reviewer, --no-seed to silence this)"}: ${names} stay empty for the reviewer (REVIEWER_ACCOUNT_POPULATED)`));
|
|
8279
8537
|
notes.push({ kind: "seed", note: "skipped", tools: emptyReads.map((t) => t.name) });
|
|
8280
8538
|
} else {
|
|
8281
8539
|
for (const c of creators) {
|
|
8282
8540
|
const args = validArgs(c, {}, examples[c.name]);
|
|
8283
8541
|
const v = localOutcome(await call3(c.name, args));
|
|
8284
8542
|
if (v.outcome !== "ok") {
|
|
8285
|
-
say(` ${mark(v.outcome)} seed via ${c.name} ${JSON.stringify(args)}: ${
|
|
8543
|
+
say(` ${mark(v.outcome)} seed via ${c.name} ${JSON.stringify(args)}: ${import_chalk28.default.dim(redactHead(v.text, 100) || v.outcome)}`);
|
|
8286
8544
|
continue;
|
|
8287
8545
|
}
|
|
8288
8546
|
const id = extractCreatedId(parseResultJson(v.text));
|
|
8289
8547
|
const idText = id ? String(id.value).slice(0, 64) : "(no id in the answer)";
|
|
8290
8548
|
seeded[c.name] = [...seeded[c.name] ?? [], idText];
|
|
8291
8549
|
notes.push({ kind: "seed", tool: c.name, note: `seeded ${id ? `${id.name}=${idText}` : "a record"}` });
|
|
8292
|
-
say(` ${
|
|
8550
|
+
say(` ${import_chalk28.default.green("\u2713")} seeded via ${c.name}: ${idText} ${import_chalk28.default.dim("(kept for the reviewer)")}`);
|
|
8293
8551
|
}
|
|
8294
8552
|
if (Object.keys(seeded).length) {
|
|
8295
8553
|
for (const t of emptyReads) {
|
|
@@ -8303,9 +8561,9 @@ async function runConnectorExercise(project, opts = {}) {
|
|
|
8303
8561
|
row.args = args;
|
|
8304
8562
|
row.resultShape = describeResult(v.text);
|
|
8305
8563
|
}
|
|
8306
|
-
say(` ${nonEmpty ?
|
|
8564
|
+
say(` ${nonEmpty ? import_chalk28.default.green("\u2713") : import_chalk28.default.yellow("\u2013")} ${t.name} after seeding: ${import_chalk28.default.dim(nonEmpty ? v.text.replace(/\s+/g, " ").slice(0, 80) : "still nothing (a different filter or tenant? mark it --empty-ok if that is correct)")}`);
|
|
8307
8565
|
}
|
|
8308
|
-
await producer(caller, { method: "PATCH", path: projectPath(proj2), body: { directory_metadata: { seeded } }, summary: "Remember what was seeded for the reviewer" }).catch((e) => say(
|
|
8566
|
+
await producer(caller, { method: "PATCH", path: projectPath(proj2), body: { directory_metadata: { seeded } }, summary: "Remember what was seeded for the reviewer" }).catch((e) => say(import_chalk28.default.dim(` (could not store the seeded ids: ${e.message})`)));
|
|
8309
8567
|
}
|
|
8310
8568
|
}
|
|
8311
8569
|
}
|
|
@@ -8323,11 +8581,11 @@ async function runConnectorExercise(project, opts = {}) {
|
|
|
8323
8581
|
for (const client of clients) {
|
|
8324
8582
|
if (opts.noClaude && client === "claude") continue;
|
|
8325
8583
|
if (client === "claude" && !claudeInstalled) {
|
|
8326
|
-
say(
|
|
8584
|
+
say(import_chalk28.default.dim(" Claude Code is not installed here: the Claude pass is skipped (TESTED_FROM_CLAUDE and EXAMPLE_PROMPTS_WORK stay open with their instruction)."));
|
|
8327
8585
|
continue;
|
|
8328
8586
|
}
|
|
8329
8587
|
if (client === "codex" && !(codex.installed && codex.loggedIn)) {
|
|
8330
|
-
say(
|
|
8588
|
+
say(import_chalk28.default.dim(` Codex CLI is ${codex.installed ? "not logged in" : "not installed"} here: the Codex pass is skipped (TESTED_FROM_OPENAI_CLIENT, EXAMPLE_PROMPTS_WORK_OPENAI and NEGATIVE_PROMPTS_HOLD stay open with their instruction).${codex.installed ? " Run `codex login` once." : " Install it: npm i -g @openai/codex, then `codex login`."}`));
|
|
8331
8589
|
continue;
|
|
8332
8590
|
}
|
|
8333
8591
|
const already = provenFor(client);
|
|
@@ -8342,9 +8600,9 @@ async function runConnectorExercise(project, opts = {}) {
|
|
|
8342
8600
|
...client === "codex" && !opts.promptsOnly ? negatives.map((p) => ({ prompt: p, kind: "negative" })) : []
|
|
8343
8601
|
];
|
|
8344
8602
|
const label3 = client === "claude" ? "Claude Code" : "Codex CLI";
|
|
8345
|
-
if (already.size) say(
|
|
8603
|
+
if (already.size) say(import_chalk28.default.dim(` ${already.size} prompt${already.size > 1 ? "s" : ""} already run through ${label3} as ${already.size > 1 ? "they were" : "it was"} typed`));
|
|
8346
8604
|
if (!runs.length) continue;
|
|
8347
|
-
say(
|
|
8605
|
+
say(import_chalk28.default.dim(` ${label3} pass: ${runs.length} one-shot${runs.length > 1 ? "s" : ""} (10\u201325 s each; read-only tools only)`));
|
|
8348
8606
|
let cfgPath = null;
|
|
8349
8607
|
if (client === "claude") {
|
|
8350
8608
|
configDir = configDir ?? import_fs5.default.mkdtempSync(import_path4.default.join(deps.tmpdir ?? import_os2.default.tmpdir(), "abz-exercise-"));
|
|
@@ -8358,7 +8616,7 @@ async function runConnectorExercise(project, opts = {}) {
|
|
|
8358
8616
|
if (x.kind === "tool") notes.push({ kind: "claude-tool", client, tool: firstRead.name, tool_called: calls.length > 0 });
|
|
8359
8617
|
else notes.push({ kind: x.kind, prompt: x.prompt, tool_called: calls.length > 0, tools: calls, client });
|
|
8360
8618
|
const good = x.kind === "negative" ? calls.length === 0 : calls.length > 0;
|
|
8361
|
-
say(` ${good ?
|
|
8619
|
+
say(` ${good ? import_chalk28.default.green("\u2713") : import_chalk28.default.yellow("\u2013")} ${client}: "${x.prompt.slice(0, 60)}" \u2192 ${calls.length ? `called ${calls.join(", ")}` : "no tool call"}${x.kind === "negative" ? calls.length ? import_chalk28.default.yellow(" (a negative prompt must hold)") : import_chalk28.default.dim(" (held)") : ""}${status2 !== 0 && status2 != null ? import_chalk28.default.dim(` (exit ${status2})`) : ""}`);
|
|
8362
8620
|
}
|
|
8363
8621
|
}
|
|
8364
8622
|
if (bearer && asMeta) userinfo = await probeUserinfo(asMeta, bearer, deps);
|
|
@@ -8412,13 +8670,13 @@ async function runConnectorExercise(project, opts = {}) {
|
|
|
8412
8670
|
if (card) {
|
|
8413
8671
|
console.log(card);
|
|
8414
8672
|
console.log();
|
|
8415
|
-
console.log(
|
|
8673
|
+
console.log(import_chalk28.default.bold(` ${summaryOf(r)}`) + import_chalk28.default.dim(` \xB7 ${pageLinkOf(r, proj2)}`));
|
|
8416
8674
|
} else {
|
|
8417
8675
|
printReport(r, false);
|
|
8418
|
-
console.log(
|
|
8676
|
+
console.log(import_chalk28.default.cyan(` Report: ${reportLink(proj2.projectId, proj2.apiVersion)}`));
|
|
8419
8677
|
}
|
|
8420
8678
|
}
|
|
8421
|
-
console.log(
|
|
8679
|
+
console.log(import_chalk28.default.dim(opts.noEmail ? " (no email for this run)" : closed?.emailed ? " (the result was emailed to you; --no-email or the project setting turns it off)" : " (no email: the run has no address to send to)"));
|
|
8422
8680
|
return summary;
|
|
8423
8681
|
}
|
|
8424
8682
|
function claudeOneShot2(spawn5, cfgPath, prompt, allowed) {
|
|
@@ -8439,10 +8697,10 @@ async function createPromptTester(caller, proj2, opts = {}) {
|
|
|
8439
8697
|
const codex = deps.codexInstalled !== void 0 ? { installed: deps.codexInstalled, loggedIn: deps.codexInstalled } : wanted.includes("codex") ? detectCodex(deps.spawn ?? import_child_process5.spawnSync) : { installed: false, loggedIn: false };
|
|
8440
8698
|
const clients = wanted.filter((c) => c === "claude" ? claudeInstalled : codex.installed && codex.loggedIn);
|
|
8441
8699
|
if (!clients.length) {
|
|
8442
|
-
say(
|
|
8700
|
+
say(import_chalk28.default.dim(` (${wanted.includes("codex") && !wanted.includes("claude") ? `Codex CLI is ${codex.installed ? "not logged in" : "not installed"}` : "Claude Code is not installed"} here: each prompt is run at the end, in the exercise)`));
|
|
8443
8701
|
return null;
|
|
8444
8702
|
}
|
|
8445
|
-
for (const missing of wanted.filter((c) => !clients.includes(c))) say(
|
|
8703
|
+
for (const missing of wanted.filter((c) => !clients.includes(c))) say(import_chalk28.default.dim(` (${missing === "claude" ? "Claude Code is not installed" : `Codex CLI is ${codex.installed ? "not logged in" : "not installed"}`} here: that pass runs at the end, in the exercise, once it is)`));
|
|
8446
8704
|
const r = await fetchDirectoryReport(caller, proj2, { probe: false });
|
|
8447
8705
|
const target2 = r.target ?? {};
|
|
8448
8706
|
const publishedUrl = r.report.project.publishedUrl;
|
|
@@ -8455,12 +8713,12 @@ async function createPromptTester(caller, proj2, opts = {}) {
|
|
|
8455
8713
|
const packet = await producer(caller, { method: "GET", path: `${projectPath(proj2)}/directory-packet?probe=0&force=1`, summary: "Reviewer account (for the login)" }).catch(() => null);
|
|
8456
8714
|
const acct = packet?.json?.test_account ?? null;
|
|
8457
8715
|
if (!acct) {
|
|
8458
|
-
say(
|
|
8716
|
+
say(import_chalk28.default.dim(" (no reviewer account yet: each prompt is run at the end, in the exercise)"));
|
|
8459
8717
|
return null;
|
|
8460
8718
|
}
|
|
8461
8719
|
const login = await headlessPasswordLogin({ mcpUrl: publishedUrl, username: acct.username, password: acct.password }, deps);
|
|
8462
8720
|
if (!login.accessToken) {
|
|
8463
|
-
say(
|
|
8721
|
+
say(import_chalk28.default.yellow(` (the reviewer could not sign in at ${login.failedAt}: ${login.detail}; each prompt is run at the end, in the exercise)`));
|
|
8464
8722
|
return null;
|
|
8465
8723
|
}
|
|
8466
8724
|
bearer = login.accessToken;
|
|
@@ -8503,7 +8761,7 @@ async function createPromptTester(caller, proj2, opts = {}) {
|
|
|
8503
8761
|
};
|
|
8504
8762
|
}
|
|
8505
8763
|
function mark(o) {
|
|
8506
|
-
return o === "ok" ?
|
|
8764
|
+
return o === "ok" ? import_chalk28.default.green("\u2713") : o === "error" ? import_chalk28.default.yellow("!") : import_chalk28.default.red("\u2717");
|
|
8507
8765
|
}
|
|
8508
8766
|
function parseResultJson(text) {
|
|
8509
8767
|
try {
|
|
@@ -8548,14 +8806,14 @@ async function runConnectorAnswers(project, opts = {}) {
|
|
|
8548
8806
|
try {
|
|
8549
8807
|
await producer(caller, { method: "PATCH", path: projectPath(proj2), body: part, summary: "Save an answer" });
|
|
8550
8808
|
} catch (e) {
|
|
8551
|
-
console.log(
|
|
8809
|
+
console.log(import_chalk28.default.yellow(` not saved: ${e instanceof Error ? e.message : String(e)}`));
|
|
8552
8810
|
}
|
|
8553
8811
|
};
|
|
8554
8812
|
let headed = opts.derived !== false;
|
|
8555
8813
|
const ask3 = async (qs) => {
|
|
8556
8814
|
if (!headed) {
|
|
8557
8815
|
headed = true;
|
|
8558
|
-
console.log(
|
|
8816
|
+
console.log(import_chalk28.default.bold(" What the portal asks that only you can answer (saved as you go):"));
|
|
8559
8817
|
}
|
|
8560
8818
|
return inquirer3.prompt(qs);
|
|
8561
8819
|
};
|
|
@@ -8610,7 +8868,7 @@ async function runConnectorAnswers(project, opts = {}) {
|
|
|
8610
8868
|
const ask22 = async (qs) => {
|
|
8611
8869
|
if (!headed2) {
|
|
8612
8870
|
headed2 = true;
|
|
8613
|
-
console.log(
|
|
8871
|
+
console.log(import_chalk28.default.bold(" What OpenAI's form asks (saved as you go):"));
|
|
8614
8872
|
}
|
|
8615
8873
|
return inquirer4.prompt(qs);
|
|
8616
8874
|
};
|
|
@@ -8669,7 +8927,7 @@ async function runConnectorAnswers(project, opts = {}) {
|
|
|
8669
8927
|
const open = current.target?.project?.authMode === "open";
|
|
8670
8928
|
const tools = current.target?.tools ?? [];
|
|
8671
8929
|
const defaults = defaultNegativePrompts2({ open, tools, serverName: typeof cm.serverName === "string" ? cm.serverName : null });
|
|
8672
|
-
console.log(
|
|
8930
|
+
console.log(import_chalk28.default.dim(" Three prompts the plugin must NOT act on (OpenAI asks for them; the exercise checks that no tool is called):"));
|
|
8673
8931
|
const negative = [];
|
|
8674
8932
|
for (let i = 0; i < 3; i++) {
|
|
8675
8933
|
const a = await ask22([{ type: "input", name: "value", message: `Negative prompt ${i + 1}:`, default: defaults[i].prompt }]);
|
|
@@ -8681,16 +8939,16 @@ async function runConnectorAnswers(project, opts = {}) {
|
|
|
8681
8939
|
}
|
|
8682
8940
|
const prompts = Array.isArray(cm.examplePrompts) ? cm.examplePrompts.filter((p) => typeof p === "string" && p.trim()) : [];
|
|
8683
8941
|
if (prompts.length >= 3 && prompts.length < 5) {
|
|
8684
|
-
console.log(
|
|
8942
|
+
console.log(import_chalk28.default.dim(` OpenAI wants five positive cases; you have ${prompts.length} prompts. ${5 - prompts.length} more a reviewer can try:`));
|
|
8685
8943
|
const extra = [...prompts];
|
|
8686
8944
|
for (let i = prompts.length; i < 5; i++) {
|
|
8687
8945
|
const a = await ask22([{ type: "input", name: "value", message: `Prompt ${i + 1} (Enter to stop here):`, validate: (v) => v.trim().length <= 128 ? true : `${v.trim().length} characters; ChatGPT allows 128` }]);
|
|
8688
8946
|
const p = a.value.trim();
|
|
8689
8947
|
if (!p) break;
|
|
8690
8948
|
if (opts.testPrompt) {
|
|
8691
|
-
console.log(
|
|
8949
|
+
console.log(import_chalk28.default.dim(" trying it\u2026"));
|
|
8692
8950
|
const res = await opts.testPrompt(p);
|
|
8693
|
-
console.log(res.called ?
|
|
8951
|
+
console.log(res.called ? import_chalk28.default.green(` \u2713 called ${res.tools.join(", ")}`) : import_chalk28.default.yellow(" \u2013 no tool call; kept (the exercise will say so)"));
|
|
8694
8952
|
}
|
|
8695
8953
|
extra.push(p);
|
|
8696
8954
|
}
|
|
@@ -8706,7 +8964,7 @@ async function runConnectorAnswers(project, opts = {}) {
|
|
|
8706
8964
|
if (Object.keys(meta).length) body.directory_metadata = meta;
|
|
8707
8965
|
if (!Object.keys(body).length) {
|
|
8708
8966
|
if (json) process.stdout.write(JSON.stringify({ project: proj2.projectId, changed: [] }) + "\n");
|
|
8709
|
-
else if (opts.derived !== false) console.log(
|
|
8967
|
+
else if (opts.derived !== false) console.log(import_chalk28.default.dim(" Nothing to record: pass flags (--use-cases, --data-handling \u2026) or run interactively."));
|
|
8710
8968
|
return;
|
|
8711
8969
|
}
|
|
8712
8970
|
await producer(caller, { method: "PATCH", path: projectPath(proj2), body, summary: "Store the portal answers" });
|
|
@@ -8715,15 +8973,15 @@ async function runConnectorAnswers(project, opts = {}) {
|
|
|
8715
8973
|
process.stdout.write(JSON.stringify({ project: proj2.projectId, changed: Object.keys({ ...answers, ...meta }), headline: r.rendered.headline, report: r.report }) + "\n");
|
|
8716
8974
|
return;
|
|
8717
8975
|
}
|
|
8718
|
-
console.log(
|
|
8719
|
-
if (opts.derived !== false) console.log(
|
|
8720
|
-
else console.log(
|
|
8976
|
+
console.log(import_chalk28.default.green(` \u2713 recorded: ${Object.keys({ ...answers, ...meta }).join(", ")}`));
|
|
8977
|
+
if (opts.derived !== false) console.log(import_chalk28.default.dim(` ${summaryOf(r) ?? r.rendered.headline}${summaryOf(r) ? ` \xB7 ${pageLinkOf(r, proj2)}` : ""}`));
|
|
8978
|
+
else console.log(import_chalk28.default.dim(` ${r.rendered.headline}`));
|
|
8721
8979
|
}
|
|
8722
|
-
var
|
|
8980
|
+
var import_chalk28, import_fs5, import_os2, import_path4, import_child_process5, DASHBOARD_BASE8, RUN_META_KEY, RUN_HEADER, projectPath, openRuns, QUESTIONS;
|
|
8723
8981
|
var init_connector_exercise = __esm({
|
|
8724
8982
|
"src/commands/connector-exercise.ts"() {
|
|
8725
8983
|
"use strict";
|
|
8726
|
-
|
|
8984
|
+
import_chalk28 = __toESM(require("chalk"));
|
|
8727
8985
|
import_fs5 = __toESM(require("fs"));
|
|
8728
8986
|
import_os2 = __toESM(require("os"));
|
|
8729
8987
|
import_path4 = __toESM(require("path"));
|
|
@@ -8907,8 +9165,8 @@ async function runConnectorDocs(project, opts = {}) {
|
|
|
8907
9165
|
how = `from ${opts.from}`;
|
|
8908
9166
|
} else if (opts.promptOut) {
|
|
8909
9167
|
import_fs6.default.writeFileSync(opts.promptOut, prompt + "\n", { mode: 384 });
|
|
8910
|
-
say(
|
|
8911
|
-
say(
|
|
9168
|
+
say(import_chalk29.default.green(` \u2713 prompt written to ${opts.promptOut}`));
|
|
9169
|
+
say(import_chalk29.default.dim(` Run it through a model that answers JSON matching the schema, save the answer, then: ${B.cli} connector docs ${proj2.projectName} --from <answer.json>`));
|
|
8912
9170
|
return;
|
|
8913
9171
|
} else {
|
|
8914
9172
|
const installed3 = deps.claudeInstalled ?? detectExternalClis().some((c) => c.kind === "claude");
|
|
@@ -8917,7 +9175,7 @@ async function runConnectorDocs(project, opts = {}) {
|
|
|
8917
9175
|
import_fs6.default.writeFileSync(file2, prompt + "\n", { mode: 384 });
|
|
8918
9176
|
throw new Error(`Claude Code is not installed here, so the documents were not generated. The prompt is at ${file2}: run it through any model that answers JSON, then \`${B.cli} connector docs ${proj2.projectName} --from <answer.json>\`; or write them on the dashboard page.`);
|
|
8919
9177
|
}
|
|
8920
|
-
say(
|
|
9178
|
+
say(import_chalk29.default.dim(" generating the three documents on Claude Code (30\u201390 s)\u2026"));
|
|
8921
9179
|
const g = generateWithClaude(prompt, deps);
|
|
8922
9180
|
if (!g.docs) throw new Error(`${g.detail}. Run again, or \`${B.cli} connector docs ${proj2.projectName} --prompt-out prompt.txt\` to use another model.`);
|
|
8923
9181
|
docs = g.docs;
|
|
@@ -8928,8 +9186,8 @@ async function runConnectorDocs(project, opts = {}) {
|
|
|
8928
9186
|
process.stdout.write(JSON.stringify({ project: proj2.projectId, changed, docs_url: fr.generated_docs_url, how }) + "\n");
|
|
8929
9187
|
return;
|
|
8930
9188
|
}
|
|
8931
|
-
console.log(
|
|
8932
|
-
console.log(` Documentation page: ${
|
|
9189
|
+
console.log(import_chalk29.default.green(` \u2713 documents stored (${how}): ${changed.join(", ")}`));
|
|
9190
|
+
console.log(` Documentation page: ${import_chalk29.default.cyan(fr.generated_docs_url)} \u2014 link it from your site; edit it on the dashboard any time.`);
|
|
8933
9191
|
await printProgressTrailer(caller, proj2);
|
|
8934
9192
|
}
|
|
8935
9193
|
async function askDocsQuestion(caller, proj2, state, interactive2, say, deps = {}) {
|
|
@@ -8938,29 +9196,29 @@ async function askDocsQuestion(caller, proj2, state, interactive2, say, deps = {
|
|
|
8938
9196
|
const a = await inquirer3.prompt([{ type: "input", name: "value", message: `Documentation for this connector on your site? Paste the URL, or N to use the generated page at ${state.generatedDocsUrl} (and link it from your site):`, default: state.docsUrl ?? "N" }]);
|
|
8939
9197
|
const v = a.value.trim();
|
|
8940
9198
|
if (!v || /^n(o)?$/i.test(v)) {
|
|
8941
|
-
say(
|
|
9199
|
+
say(import_chalk29.default.dim(" using the generated page"));
|
|
8942
9200
|
try {
|
|
8943
9201
|
await runConnectorDocs(proj2.projectId, { deps, yes: true, team: proj2.teamId });
|
|
8944
9202
|
} catch (e) {
|
|
8945
|
-
say(
|
|
9203
|
+
say(import_chalk29.default.yellow(` ${e instanceof Error ? e.message : String(e)}`));
|
|
8946
9204
|
await producer(caller, { method: "PUT", path: `${projectPath2(proj2)}/directory-documents`, body: { use_generated_docs_url: true }, summary: "Point docs at the generated page" });
|
|
8947
9205
|
}
|
|
8948
9206
|
return true;
|
|
8949
9207
|
}
|
|
8950
9208
|
const check = await verifyDocsUrl(v, state.serverName, deps.fetch);
|
|
8951
9209
|
if (!check.ok) {
|
|
8952
|
-
say(
|
|
9210
|
+
say(import_chalk29.default.yellow(` ${v} is not usable as documentation yet: ${check.reason}. Not stored.`));
|
|
8953
9211
|
return false;
|
|
8954
9212
|
}
|
|
8955
9213
|
await producer(caller, { method: "PATCH", path: projectPath2(proj2), body: { directory_metadata: { docs_url: v, docs_confirmed: true } }, summary: "Store the documentation URL" });
|
|
8956
|
-
say(
|
|
9214
|
+
say(import_chalk29.default.green(` \u2713 docs: ${v} (live, names ${state.serverName ?? "the server"})`));
|
|
8957
9215
|
return true;
|
|
8958
9216
|
}
|
|
8959
|
-
var
|
|
9217
|
+
var import_chalk29, import_fs6, import_os3, import_path5, import_child_process6, projectPath2;
|
|
8960
9218
|
var init_connector_docs = __esm({
|
|
8961
9219
|
"src/commands/connector-docs.ts"() {
|
|
8962
9220
|
"use strict";
|
|
8963
|
-
|
|
9221
|
+
import_chalk29 = __toESM(require("chalk"));
|
|
8964
9222
|
init_directory();
|
|
8965
9223
|
import_fs6 = __toESM(require("fs"));
|
|
8966
9224
|
import_os3 = __toESM(require("os"));
|
|
@@ -9036,24 +9294,24 @@ var init_plugin_package = __esm({
|
|
|
9036
9294
|
// src/lib/agent-chat.ts
|
|
9037
9295
|
async function runAgentChatRepl(opts) {
|
|
9038
9296
|
const { title, subtitle, endpoint, buildBody, seedPrompt, summarizeProposal, commands } = opts;
|
|
9039
|
-
console.log("\n" +
|
|
9040
|
-
if (subtitle) console.log(
|
|
9297
|
+
console.log("\n" + import_chalk30.default.cyan.bold(title));
|
|
9298
|
+
if (subtitle) console.log(import_chalk30.default.dim(subtitle));
|
|
9041
9299
|
const messages = [];
|
|
9042
9300
|
const ctx = { proposal: null, lastData: null, log: (s) => console.log(s) };
|
|
9043
9301
|
const cmdByName = new Map(commands.map((c) => [c.name, c]));
|
|
9044
9302
|
const footer = () => {
|
|
9045
9303
|
const parts = [
|
|
9046
|
-
|
|
9047
|
-
...commands.map((c) =>
|
|
9048
|
-
|
|
9049
|
-
|
|
9050
|
-
|
|
9304
|
+
import_chalk30.default.dim("type to chat/refine"),
|
|
9305
|
+
...commands.map((c) => import_chalk30.default.dim(`/${c.name} ${c.describe}`)),
|
|
9306
|
+
import_chalk30.default.dim("/show"),
|
|
9307
|
+
import_chalk30.default.dim("/exit when done"),
|
|
9308
|
+
import_chalk30.default.dim("/drop discard changes")
|
|
9051
9309
|
];
|
|
9052
|
-
return " " +
|
|
9310
|
+
return " " + import_chalk30.default.dim("[ ") + parts.join(import_chalk30.default.dim(" \xB7 ")) + import_chalk30.default.dim(" ]");
|
|
9053
9311
|
};
|
|
9054
9312
|
async function turn(content) {
|
|
9055
9313
|
messages.push({ role: "user", content });
|
|
9056
|
-
process.stdout.write(
|
|
9314
|
+
process.stdout.write(import_chalk30.default.dim(" \u2026thinking\n"));
|
|
9057
9315
|
const { status: status2, data } = await agentCall(endpoint, "POST", { messages, ...buildBody(messages) });
|
|
9058
9316
|
if (status2 === 402 || status2 === 503 || parseProblem(data)) {
|
|
9059
9317
|
await reportRefusal(status2, data, { cli: B.cli });
|
|
@@ -9062,7 +9320,7 @@ async function runAgentChatRepl(opts) {
|
|
|
9062
9320
|
return;
|
|
9063
9321
|
}
|
|
9064
9322
|
if (status2 >= 400 || !data) {
|
|
9065
|
-
console.log(
|
|
9323
|
+
console.log(import_chalk30.default.red(` Error (${status2}): ${(data && data.error) ?? "request failed"}
|
|
9066
9324
|
`));
|
|
9067
9325
|
messages.pop();
|
|
9068
9326
|
return;
|
|
@@ -9070,24 +9328,24 @@ async function runAgentChatRepl(opts) {
|
|
|
9070
9328
|
ctx.lastData = data;
|
|
9071
9329
|
const reply = data.reply ?? data.message ?? "(no reply)";
|
|
9072
9330
|
messages.push({ role: "assistant", content: reply });
|
|
9073
|
-
console.log("\n" +
|
|
9331
|
+
console.log("\n" + import_chalk30.default.cyan("agent \u203A ") + reply + "\n");
|
|
9074
9332
|
if (data.proposal) {
|
|
9075
9333
|
ctx.proposal = data.proposal;
|
|
9076
9334
|
const summary = summarizeProposal?.(data);
|
|
9077
|
-
if (summary) console.log(
|
|
9335
|
+
if (summary) console.log(import_chalk30.default.yellow(" " + summary));
|
|
9078
9336
|
const applies = commands.filter((c) => c.needsProposal);
|
|
9079
9337
|
const primary = applies[applies.length - 1];
|
|
9080
9338
|
if (primary) {
|
|
9081
|
-
console.log(
|
|
9339
|
+
console.log(import_chalk30.default.bold.green(" \u279C Next: type ") + import_chalk30.default.bold.cyan(`/${primary.name}`) + import_chalk30.default.bold.green(" to apply this, then ") + import_chalk30.default.bold.cyan("/exit") + import_chalk30.default.bold.green(" to leave."));
|
|
9082
9340
|
const rest = applies.slice(0, -1).map((c) => `/${c.name} = ${c.describe}`).join(" \xB7 ");
|
|
9083
|
-
console.log(
|
|
9341
|
+
console.log(import_chalk30.default.dim(` (or keep chatting to refine${rest ? ` \xB7 ${rest}` : ""} \xB7 /show = inspect)`));
|
|
9084
9342
|
}
|
|
9085
9343
|
console.log();
|
|
9086
9344
|
}
|
|
9087
9345
|
const line = formatBilling(data.billing);
|
|
9088
9346
|
if (line) {
|
|
9089
9347
|
const llm = data.llm;
|
|
9090
|
-
console.log(
|
|
9348
|
+
console.log(import_chalk30.default.dim(` ${line}${llm?.model ? ` \xB7 ${llm.model}` : ""}`));
|
|
9091
9349
|
}
|
|
9092
9350
|
}
|
|
9093
9351
|
const rl = import_readline.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -9095,31 +9353,31 @@ async function runAgentChatRepl(opts) {
|
|
|
9095
9353
|
await turn(seedPrompt);
|
|
9096
9354
|
for (; ; ) {
|
|
9097
9355
|
console.log(footer());
|
|
9098
|
-
const line = (await ask3(
|
|
9356
|
+
const line = (await ask3(import_chalk30.default.green("you \u203A "))).trim();
|
|
9099
9357
|
if (!line) continue;
|
|
9100
9358
|
const lower = line.toLowerCase();
|
|
9101
9359
|
if (line === "/exit" || line === "/quit" || lower === "exit" || lower === "quit" || lower === "q") {
|
|
9102
|
-
console.log(
|
|
9360
|
+
console.log(import_chalk30.default.dim(" Bye \u2014 anything you enabled or published stays in place.\n"));
|
|
9103
9361
|
break;
|
|
9104
9362
|
}
|
|
9105
9363
|
if (line === "/drop") {
|
|
9106
|
-
console.log(
|
|
9364
|
+
console.log(import_chalk30.default.dim(" Dropped the pending proposal \u2014 rules you already enabled/published are untouched.\n"));
|
|
9107
9365
|
break;
|
|
9108
9366
|
}
|
|
9109
9367
|
if (line === "/show") {
|
|
9110
|
-
if (!ctx.proposal) console.log(
|
|
9368
|
+
if (!ctx.proposal) console.log(import_chalk30.default.dim(" No proposal yet \u2014 keep chatting until the agent proposes one.\n"));
|
|
9111
9369
|
else console.log("\n" + JSON.stringify(ctx.proposal, null, 2) + "\n");
|
|
9112
9370
|
continue;
|
|
9113
9371
|
}
|
|
9114
9372
|
if (line.startsWith("/")) {
|
|
9115
9373
|
const cmd = cmdByName.get(line.slice(1));
|
|
9116
9374
|
if (!cmd) {
|
|
9117
|
-
console.log(
|
|
9375
|
+
console.log(import_chalk30.default.red(` Unknown command "${line}". Try one of: ${commands.map((c) => "/" + c.name).join(", ")}, /show, /drop
|
|
9118
9376
|
`));
|
|
9119
9377
|
continue;
|
|
9120
9378
|
}
|
|
9121
9379
|
if (cmd.needsProposal && !ctx.proposal) {
|
|
9122
|
-
console.log(
|
|
9380
|
+
console.log(import_chalk30.default.yellow(" No proposal yet \u2014 keep chatting until the agent generates one, then try again.\n"));
|
|
9123
9381
|
continue;
|
|
9124
9382
|
}
|
|
9125
9383
|
await cmd.run(ctx);
|
|
@@ -9129,12 +9387,12 @@ async function runAgentChatRepl(opts) {
|
|
|
9129
9387
|
}
|
|
9130
9388
|
rl.close();
|
|
9131
9389
|
}
|
|
9132
|
-
var import_readline,
|
|
9390
|
+
var import_readline, import_chalk30;
|
|
9133
9391
|
var init_agent_chat = __esm({
|
|
9134
9392
|
"src/lib/agent-chat.ts"() {
|
|
9135
9393
|
"use strict";
|
|
9136
9394
|
import_readline = __toESM(require("readline"));
|
|
9137
|
-
|
|
9395
|
+
import_chalk30 = __toESM(require("chalk"));
|
|
9138
9396
|
init_problem();
|
|
9139
9397
|
init_brand();
|
|
9140
9398
|
init_api();
|
|
@@ -9190,11 +9448,11 @@ async function runMcp(projectArg, apiVersionArg, opts) {
|
|
|
9190
9448
|
const spec = ctx.proposal;
|
|
9191
9449
|
const pub = await agentCall(`/projects/${projectId}/${apiVersion}/mcp/spec`, "PUT", { environment, spec });
|
|
9192
9450
|
if (pub.status >= 400) {
|
|
9193
|
-
ctx.log(
|
|
9451
|
+
ctx.log(import_chalk31.default.red(` Publish failed (${pub.status}): ${pub.data?.error ?? ""}
|
|
9194
9452
|
`));
|
|
9195
9453
|
return;
|
|
9196
9454
|
}
|
|
9197
|
-
ctx.log(
|
|
9455
|
+
ctx.log(import_chalk31.default.green(` \u2713 Published MCP server \u2014 ${mcpHost2}.mcp.apiblaze.com/${apiVersion}/${environment}.
|
|
9198
9456
|
`));
|
|
9199
9457
|
}
|
|
9200
9458
|
await runAgentChatRepl({
|
|
@@ -9425,7 +9683,7 @@ function renderOps(cat) {
|
|
|
9425
9683
|
});
|
|
9426
9684
|
const w = Math.min(40, Math.max(4, ...rows.map((r) => r.name.length)));
|
|
9427
9685
|
const rw = Math.min(40, Math.max(5, ...rows.map((r) => r.route.length)));
|
|
9428
|
-
return rows.map((r) => ` ${r.name.padEnd(w)} ${r.route.padEnd(rw)} ${r.eff}${r.custom ?
|
|
9686
|
+
return rows.map((r) => ` ${r.name.padEnd(w)} ${r.route.padEnd(rw)} ${r.eff}${r.custom ? import_chalk31.default.yellow(" (override)") : ""}`);
|
|
9429
9687
|
}
|
|
9430
9688
|
async function resolveMcpTarget(projectArg, opts) {
|
|
9431
9689
|
const creds = loadCredentials();
|
|
@@ -9467,7 +9725,7 @@ async function runMcpPrompts(action, project, name, opts) {
|
|
|
9467
9725
|
if (action === "list") {
|
|
9468
9726
|
if (opts.json) return void console.log(JSON.stringify({ environment: t.environment, prompts }, null, 2));
|
|
9469
9727
|
if (!prompts.length) {
|
|
9470
|
-
console.log(
|
|
9728
|
+
console.log(import_chalk31.default.dim(`
|
|
9471
9729
|
No prompts on ${t.projectId} (${t.environment}). Prompts are drafted by the MCP agent (\`${B.cli} agent mcp ${t.projectId}\`) or added with \`${B.cli} mcp prompts set\`.
|
|
9472
9730
|
`));
|
|
9473
9731
|
return;
|
|
@@ -9475,7 +9733,7 @@ async function runMcpPrompts(action, project, name, opts) {
|
|
|
9475
9733
|
console.log("");
|
|
9476
9734
|
for (const p of prompts) {
|
|
9477
9735
|
const args = (p.arguments ?? []).map((a) => a.required ? a.name : `${a.name}?`).join(", ");
|
|
9478
|
-
console.log(` ${
|
|
9736
|
+
console.log(` ${import_chalk31.default.bold(p.name)}${args ? import_chalk31.default.dim(`(${args})`) : ""}${p.title || p.description ? ` ${p.title ?? p.description}` : ""}`);
|
|
9479
9737
|
}
|
|
9480
9738
|
console.log("");
|
|
9481
9739
|
return;
|
|
@@ -9486,9 +9744,9 @@ async function runMcpPrompts(action, project, name, opts) {
|
|
|
9486
9744
|
if (!p) throw new Error(`No prompt named "${name}". See \`${B.cli} mcp prompts list ${project}\`.`);
|
|
9487
9745
|
if (opts.json) return void console.log(JSON.stringify(p, null, 2));
|
|
9488
9746
|
console.log(`
|
|
9489
|
-
${
|
|
9747
|
+
${import_chalk31.default.bold(p.name)}${p.title ? ` \u2014 ${p.title}` : ""}`);
|
|
9490
9748
|
if (p.description) console.log(` ${p.description}`);
|
|
9491
|
-
for (const a of p.arguments ?? []) console.log(
|
|
9749
|
+
for (const a of p.arguments ?? []) console.log(import_chalk31.default.dim(` ${a.name}${a.required ? " (required)" : ""}${a.description ? `: ${a.description}` : ""}`));
|
|
9492
9750
|
for (const m of p.messages) console.log(`
|
|
9493
9751
|
[${m.role}] ${m.text}`);
|
|
9494
9752
|
console.log("");
|
|
@@ -9509,7 +9767,7 @@ async function runMcpPrompts(action, project, name, opts) {
|
|
|
9509
9767
|
}
|
|
9510
9768
|
await saveCatalogue(t, next);
|
|
9511
9769
|
if (opts.json) return void console.log(JSON.stringify({ saved: true, environment: t.environment, prompts: next.prompts }, null, 2));
|
|
9512
|
-
console.log(
|
|
9770
|
+
console.log(import_chalk31.default.green(` \u2713 ${action === "set" ? "Saved" : "Removed"} prompt "${name}" on ${t.projectId} (${t.environment}).`));
|
|
9513
9771
|
}
|
|
9514
9772
|
async function runMcpOps(action, project, tool, opts) {
|
|
9515
9773
|
const t = await resolveMcpTarget(project, opts);
|
|
@@ -9533,13 +9791,13 @@ async function runMcpOps(action, project, tool, opts) {
|
|
|
9533
9791
|
});
|
|
9534
9792
|
return void console.log(JSON.stringify({ environment: t.environment, instruction: cat.contextNote ?? null, tools, hidden: (hidden ?? []).map((c) => ({ method: c.method.toUpperCase(), path: c.path, name: c.name, group: c.group, description: c.description ?? null })) }, null, 2));
|
|
9535
9793
|
}
|
|
9536
|
-
console.log(
|
|
9794
|
+
console.log(import_chalk31.default.dim(`
|
|
9537
9795
|
${cat.tools.length} tool(s) on ${t.projectId} (${t.environment}). Defaults come from the HTTP method: GET/HEAD/OPTIONS read-only, PUT/DELETE idempotent, DELETE destructive + asks to confirm.
|
|
9538
9796
|
`));
|
|
9539
9797
|
for (const l of renderOps(cat)) console.log(l);
|
|
9540
|
-
console.log(
|
|
9798
|
+
console.log(import_chalk31.default.dim(`
|
|
9541
9799
|
Override: ${B.cli} mcp ops set ${project} <tool> --name | --description | --read-only | --destructive | --confirm | --no-confirm | --always-async \xB7 Reset: ${B.cli} mcp ops reset ${project} <tool>`));
|
|
9542
|
-
if (hidden?.length) console.log(
|
|
9800
|
+
if (hidden?.length) console.log(import_chalk31.default.dim(` ${hidden.length} operation(s) of the spec are not tools (${B.cli} mcp ops list ${project} --json shows them; ${B.cli} mcp ops show ${project} <tool> adds one).`));
|
|
9543
9801
|
console.log("");
|
|
9544
9802
|
return;
|
|
9545
9803
|
}
|
|
@@ -9548,7 +9806,7 @@ async function runMcpOps(action, project, tool, opts) {
|
|
|
9548
9806
|
const next2 = withoutTool(cat, tool);
|
|
9549
9807
|
await saveCatalogue(t, next2);
|
|
9550
9808
|
if (opts.json) return void console.log(JSON.stringify({ saved: true, hidden: tool, tools: next2.tools.length }, null, 2));
|
|
9551
|
-
console.log(
|
|
9809
|
+
console.log(import_chalk31.default.green(` \u2713 ${tool} is no longer a tool. ${next2.tools.length} tool(s) remain; \`${B.cli} mcp ops show ${project} ${tool}\` brings it back.`));
|
|
9552
9810
|
return;
|
|
9553
9811
|
}
|
|
9554
9812
|
if (action === "show") {
|
|
@@ -9562,7 +9820,7 @@ async function runMcpOps(action, project, tool, opts) {
|
|
|
9562
9820
|
const next2 = withTool(cat, c);
|
|
9563
9821
|
await saveCatalogue(t, next2);
|
|
9564
9822
|
if (opts.json) return void console.log(JSON.stringify({ saved: true, shown: c.name, method: c.method.toUpperCase(), path: c.path, tools: next2.tools.length }, null, 2));
|
|
9565
|
-
console.log(
|
|
9823
|
+
console.log(import_chalk31.default.green(` \u2713 ${c.name} (${c.method.toUpperCase()} ${c.path}) is a tool. ${next2.tools.length} tool(s). Give it a description: \`${B.cli} mcp ops set ${project} ${c.name} --description "\u2026"\`.`));
|
|
9566
9824
|
return;
|
|
9567
9825
|
}
|
|
9568
9826
|
let next = cat;
|
|
@@ -9584,7 +9842,7 @@ async function runMcpOps(action, project, tool, opts) {
|
|
|
9584
9842
|
const eff = effectiveBehavior(method, next.overrides?.[finalName]);
|
|
9585
9843
|
const edited = next.tools.find((x) => x.name === finalName);
|
|
9586
9844
|
if (opts.json) return void console.log(JSON.stringify({ saved: true, tool: finalName, ...finalName !== tool ? { renamed_from: tool } : {}, description: edited?.description ?? null, override: next.overrides?.[finalName] ?? null, effective: eff }, null, 2));
|
|
9587
|
-
console.log(
|
|
9845
|
+
console.log(import_chalk31.default.green(` \u2713 ${finalName}${finalName !== tool ? ` (was ${tool})` : ""}: ${flagsLine(eff)}${action === "reset" ? " (automatic default)" : ""}${opts.description !== void 0 ? ` \u2014 ${edited?.description}` : ""}`));
|
|
9588
9846
|
}
|
|
9589
9847
|
async function runMcpInstructions(action, project, text, opts) {
|
|
9590
9848
|
const t = await resolveMcpTarget(project, opts);
|
|
@@ -9593,7 +9851,7 @@ async function runMcpInstructions(action, project, text, opts) {
|
|
|
9593
9851
|
if (opts.json) return void console.log(JSON.stringify({ environment: t.environment, instruction: cat.contextNote ?? null }, null, 2));
|
|
9594
9852
|
console.log(cat.contextNote ? `
|
|
9595
9853
|
${cat.contextNote}
|
|
9596
|
-
` :
|
|
9854
|
+
` : import_chalk31.default.dim(`
|
|
9597
9855
|
No instruction on ${t.projectId} (${t.environment}). Set one: ${B.cli} mcp instructions set ${project} "What this MCP is for, how to start, what it will not do."
|
|
9598
9856
|
`));
|
|
9599
9857
|
return;
|
|
@@ -9610,7 +9868,7 @@ async function runMcpInstructions(action, project, text, opts) {
|
|
|
9610
9868
|
const next = withInstruction(cat, body);
|
|
9611
9869
|
await saveCatalogue(t, next);
|
|
9612
9870
|
if (opts.json) return void console.log(JSON.stringify({ saved: true, environment: t.environment, instruction: next.contextNote ?? null }, null, 2));
|
|
9613
|
-
console.log(
|
|
9871
|
+
console.log(import_chalk31.default.green(next.contextNote ? ` \u2713 Instruction saved (${next.contextNote.length} characters).` : " \u2713 Instruction cleared."));
|
|
9614
9872
|
}
|
|
9615
9873
|
async function runMcpCatalogue(project, opts) {
|
|
9616
9874
|
const t = await resolveMcpTarget(project, opts);
|
|
@@ -9618,7 +9876,7 @@ async function runMcpCatalogue(project, opts) {
|
|
|
9618
9876
|
const candidates = await loadCandidates(t).catch(() => null);
|
|
9619
9877
|
const hidden = candidates ? hiddenCandidates(candidates, cat) : null;
|
|
9620
9878
|
if (opts.json) return void console.log(JSON.stringify({ environment: t.environment, catalogue: cat, hidden: (hidden ?? []).map((c) => ({ method: c.method.toUpperCase(), path: c.path, name: c.name, group: c.group })) }, null, 2));
|
|
9621
|
-
console.log(
|
|
9879
|
+
console.log(import_chalk31.default.dim(`
|
|
9622
9880
|
${t.projectId} (${t.environment}) \u2014 ${cat.tools.length} tool(s)
|
|
9623
9881
|
`));
|
|
9624
9882
|
for (const l of renderCatalogue(cat, { hidden })) console.log(` ${l}`);
|
|
@@ -9631,17 +9889,17 @@ async function runMcpUi(project, tool, opts) {
|
|
|
9631
9889
|
const uri = uiResourceUri(t.projectId, tool);
|
|
9632
9890
|
if (opts.json) return void console.log(JSON.stringify({ tool, uri, kind: "automatic" }, null, 2));
|
|
9633
9891
|
console.log(`
|
|
9634
|
-
${
|
|
9892
|
+
${import_chalk31.default.bold(uri)}
|
|
9635
9893
|
`);
|
|
9636
|
-
console.log(
|
|
9637
|
-
console.log(
|
|
9894
|
+
console.log(import_chalk31.default.dim(" This is the automatic viewer: MCP Apps clients render the tool's result with a generic UI built"));
|
|
9895
|
+
console.log(import_chalk31.default.dim(" from its response schema \u2014 nothing to configure. Uploading your own HTML for it comes later.\n"));
|
|
9638
9896
|
}
|
|
9639
|
-
var import_fs8,
|
|
9897
|
+
var import_fs8, import_chalk31, PROMPT_NAME_RE, TOOL_NAME_RE, MAX_INSTRUCTION;
|
|
9640
9898
|
var init_mcp = __esm({
|
|
9641
9899
|
"src/commands/mcp.ts"() {
|
|
9642
9900
|
"use strict";
|
|
9643
9901
|
import_fs8 = __toESM(require("fs"));
|
|
9644
|
-
|
|
9902
|
+
import_chalk31 = __toESM(require("chalk"));
|
|
9645
9903
|
init_brand();
|
|
9646
9904
|
init_auth();
|
|
9647
9905
|
init_api();
|
|
@@ -10257,24 +10515,24 @@ ready.then((c) => {
|
|
|
10257
10515
|
|
|
10258
10516
|
// src/lib/log-view.ts
|
|
10259
10517
|
function statusColor(status2) {
|
|
10260
|
-
if (status2 < 300) return
|
|
10261
|
-
if (status2 < 400) return
|
|
10262
|
-
if (status2 < 500) return
|
|
10263
|
-
return
|
|
10518
|
+
if (status2 < 300) return import_chalk33.default.green;
|
|
10519
|
+
if (status2 < 400) return import_chalk33.default.blue;
|
|
10520
|
+
if (status2 < 500) return import_chalk33.default.yellow;
|
|
10521
|
+
return import_chalk33.default.red;
|
|
10264
10522
|
}
|
|
10265
10523
|
function methodColor(method) {
|
|
10266
10524
|
switch ((method || "").toUpperCase()) {
|
|
10267
10525
|
case "GET":
|
|
10268
|
-
return
|
|
10526
|
+
return import_chalk33.default.green;
|
|
10269
10527
|
case "POST":
|
|
10270
|
-
return
|
|
10528
|
+
return import_chalk33.default.blue;
|
|
10271
10529
|
case "PUT":
|
|
10272
10530
|
case "PATCH":
|
|
10273
|
-
return
|
|
10531
|
+
return import_chalk33.default.yellow;
|
|
10274
10532
|
case "DELETE":
|
|
10275
|
-
return
|
|
10533
|
+
return import_chalk33.default.red;
|
|
10276
10534
|
default:
|
|
10277
|
-
return
|
|
10535
|
+
return import_chalk33.default.white;
|
|
10278
10536
|
}
|
|
10279
10537
|
}
|
|
10280
10538
|
function clock(ts2) {
|
|
@@ -10285,12 +10543,12 @@ function routeCell(route) {
|
|
|
10285
10543
|
return (r.length > ROUTE_W ? `\u2026${r.slice(-(ROUTE_W - 1))}` : r).padEnd(ROUTE_W);
|
|
10286
10544
|
}
|
|
10287
10545
|
function formatRow(r) {
|
|
10288
|
-
const time =
|
|
10546
|
+
const time = import_chalk33.default.dim(clock(r.ts));
|
|
10289
10547
|
const method = methodColor(r.method)(String(r.method || "\u2014").padEnd(6));
|
|
10290
10548
|
const route = routeCell(String(r.route || "\u2014"));
|
|
10291
10549
|
const status2 = statusColor(r.status)(String(r.status ?? "\u2014").padStart(3));
|
|
10292
|
-
const ms2 =
|
|
10293
|
-
const step = r.error_step ?
|
|
10550
|
+
const ms2 = import_chalk33.default.dim(`${r.duration_ms}ms`.padStart(8));
|
|
10551
|
+
const step = r.error_step ? import_chalk33.default.dim(` ${r.error_step}`) : "";
|
|
10294
10552
|
return `${time} ${method} ${route} ${status2} ${ms2}${step}`;
|
|
10295
10553
|
}
|
|
10296
10554
|
function printRow(r, json) {
|
|
@@ -10344,13 +10602,13 @@ async function runTailSession(opts) {
|
|
|
10344
10602
|
if (!held.length && !heldRowsDropped && !heldNotesDropped) return;
|
|
10345
10603
|
const rows = held.filter((h) => h.kind === "row").length;
|
|
10346
10604
|
const total = rows + heldRowsDropped;
|
|
10347
|
-
if (total) console.log(
|
|
10605
|
+
if (total) console.log(import_chalk33.default.dim(`\u2500\u2500\u2500 ${total} request${total === 1 ? "" : "s"} arrived while paused \u2500\u2500\u2500`));
|
|
10348
10606
|
for (const h of held) {
|
|
10349
10607
|
if (h.kind === "row") printRow(h.row, opts.json);
|
|
10350
10608
|
else console.error(h.msg);
|
|
10351
10609
|
}
|
|
10352
10610
|
if (heldRowsDropped || heldNotesDropped) {
|
|
10353
|
-
console.log(
|
|
10611
|
+
console.log(import_chalk33.default.dim(`\u2500\u2500\u2500 ${heldRowsDropped} older one${heldRowsDropped === 1 ? "" : "s"} not shown (pause buffer full) \u2014 browse them with \`logs list\` \u2500\u2500\u2500`));
|
|
10354
10612
|
}
|
|
10355
10613
|
held.length = 0;
|
|
10356
10614
|
heldRowsDropped = 0;
|
|
@@ -10364,7 +10622,7 @@ async function runTailSession(opts) {
|
|
|
10364
10622
|
activeSocket?.close();
|
|
10365
10623
|
} catch {
|
|
10366
10624
|
}
|
|
10367
|
-
if (!opts.json) console.log(
|
|
10625
|
+
if (!opts.json) console.log(import_chalk33.default.dim("\nStopped."));
|
|
10368
10626
|
process.exit(0);
|
|
10369
10627
|
}
|
|
10370
10628
|
function onKeypress(_s, key) {
|
|
@@ -10392,17 +10650,17 @@ async function runTailSession(opts) {
|
|
|
10392
10650
|
async function openInspector() {
|
|
10393
10651
|
if (paused) return;
|
|
10394
10652
|
if (recent.length === 0) {
|
|
10395
|
-
console.log(
|
|
10653
|
+
console.log(import_chalk33.default.dim(" nothing captured yet \u2014 waiting for a request\u2026"));
|
|
10396
10654
|
return;
|
|
10397
10655
|
}
|
|
10398
10656
|
paused = true;
|
|
10399
10657
|
unbindKeys();
|
|
10400
10658
|
try {
|
|
10401
10659
|
const { default: inquirer3 } = await import("inquirer");
|
|
10402
|
-
console.log(
|
|
10660
|
+
console.log(import_chalk33.default.dim("\n\u2500\u2500\u2500 paused \xB7 still capturing in the background \u2500\u2500\u2500"));
|
|
10403
10661
|
for (; ; ) {
|
|
10404
10662
|
const choices = recent.slice(-TAIL_INSPECT_CHOICES).reverse().map((r) => ({ name: formatRow(r), value: r.request_id }));
|
|
10405
|
-
choices.push({ name:
|
|
10663
|
+
choices.push({ name: import_chalk33.default.dim("\u2039resume tail\u203A"), value: "__resume__" });
|
|
10406
10664
|
const { picked } = await inquirer3.prompt([{
|
|
10407
10665
|
type: "list",
|
|
10408
10666
|
name: "picked",
|
|
@@ -10413,12 +10671,12 @@ async function runTailSession(opts) {
|
|
|
10413
10671
|
if (picked === "__resume__") break;
|
|
10414
10672
|
const row = recent.find((r) => r.request_id === picked);
|
|
10415
10673
|
if (row) printDetail(row, opts.detail);
|
|
10416
|
-
else console.log(
|
|
10674
|
+
else console.log(import_chalk33.default.dim(" that request has scrolled out of the tail buffer \u2014 find it with `logs list`.\n"));
|
|
10417
10675
|
}
|
|
10418
10676
|
} finally {
|
|
10419
10677
|
paused = false;
|
|
10420
10678
|
flushHeld();
|
|
10421
|
-
console.log(
|
|
10679
|
+
console.log(import_chalk33.default.dim("\u2500\u2500\u2500 resumed \u2500\u2500\u2500"));
|
|
10422
10680
|
bindKeys();
|
|
10423
10681
|
}
|
|
10424
10682
|
}
|
|
@@ -10428,12 +10686,12 @@ async function runTailSession(opts) {
|
|
|
10428
10686
|
if (reconnects >= MAX_RECONNECTS) {
|
|
10429
10687
|
paused = false;
|
|
10430
10688
|
flushHeld();
|
|
10431
|
-
console.error(
|
|
10689
|
+
console.error(import_chalk33.default.red(`Connection lost \u2014 giving up after repeated reconnects${lastFailure ? ` (last error: ${lastFailure})` : ""}.`));
|
|
10432
10690
|
process.exit(1);
|
|
10433
10691
|
}
|
|
10434
10692
|
const delay = Math.min(1e3 * 2 ** reconnects, 15e3);
|
|
10435
10693
|
reconnects++;
|
|
10436
|
-
diag(
|
|
10694
|
+
diag(import_chalk33.default.dim(`disconnected${lastFailure ? ` (${lastFailure})` : ""} \u2014 reconnecting in ${Math.round(delay / 1e3)}s\u2026`));
|
|
10437
10695
|
setTimeout(() => {
|
|
10438
10696
|
if (!closed) void connect();
|
|
10439
10697
|
}, delay);
|
|
@@ -10444,7 +10702,7 @@ async function runTailSession(opts) {
|
|
|
10444
10702
|
url = await opts.mintTicket();
|
|
10445
10703
|
} catch (err) {
|
|
10446
10704
|
if (!everConnected) {
|
|
10447
|
-
console.error(
|
|
10705
|
+
console.error(import_chalk33.default.red(`Could not start tail: ${err.message}`));
|
|
10448
10706
|
process.exit(1);
|
|
10449
10707
|
}
|
|
10450
10708
|
lastFailure = `ticket mint failed: ${err.message}`;
|
|
@@ -10457,7 +10715,7 @@ async function runTailSession(opts) {
|
|
|
10457
10715
|
} catch (err) {
|
|
10458
10716
|
lastFailure = `bad tail URL: ${err.message}`;
|
|
10459
10717
|
if (!everConnected) {
|
|
10460
|
-
console.error(
|
|
10718
|
+
console.error(import_chalk33.default.red(`Could not start tail: ${lastFailure}`));
|
|
10461
10719
|
process.exit(1);
|
|
10462
10720
|
}
|
|
10463
10721
|
scheduleReconnect();
|
|
@@ -10472,9 +10730,9 @@ async function runTailSession(opts) {
|
|
|
10472
10730
|
lastInbound = Date.now();
|
|
10473
10731
|
lastFailure = "";
|
|
10474
10732
|
if (isReconnect) {
|
|
10475
|
-
diag(
|
|
10733
|
+
diag(import_chalk33.default.dim("reconnected \u2014 requests during the gap were not replayed (browse them with `logs list`)."));
|
|
10476
10734
|
} else {
|
|
10477
|
-
diag(
|
|
10735
|
+
diag(import_chalk33.default.dim(interactive2 ? "[Enter] inspect a request \xB7 [Ctrl+C] stop\n" : "Ctrl+C to stop."));
|
|
10478
10736
|
}
|
|
10479
10737
|
bindKeys();
|
|
10480
10738
|
pingTimer = setInterval(() => {
|
|
@@ -10501,7 +10759,7 @@ async function runTailSession(opts) {
|
|
|
10501
10759
|
return;
|
|
10502
10760
|
}
|
|
10503
10761
|
if (frame?.type === "ceiling") {
|
|
10504
|
-
diag(
|
|
10762
|
+
diag(import_chalk33.default.yellow(
|
|
10505
10763
|
`\u26A0 volume ceiling \u2014 ${Number(frame.dropped_this_hour) || 0} rows dropped this hour; errors keep streaming.`
|
|
10506
10764
|
));
|
|
10507
10765
|
return;
|
|
@@ -10577,10 +10835,10 @@ async function fetchPage(fetchWindow3, params, notices) {
|
|
|
10577
10835
|
return { rows, nextCursor: res.next_cursor ?? void 0 };
|
|
10578
10836
|
}
|
|
10579
10837
|
if (hop >= MAX_EMPTY_HOPS) {
|
|
10580
|
-
console.error(
|
|
10838
|
+
console.error(import_chalk33.default.yellow("\u26A0 stopped after scanning a long empty span \u2014 older rows may still exist."));
|
|
10581
10839
|
return { rows: [], nextCursor: res.next_cursor, fuseTripped: true };
|
|
10582
10840
|
}
|
|
10583
|
-
if (hop === 0) console.error(
|
|
10841
|
+
if (hop === 0) console.error(import_chalk33.default.dim("searching older history\u2026"));
|
|
10584
10842
|
cursor = res.next_cursor;
|
|
10585
10843
|
}
|
|
10586
10844
|
}
|
|
@@ -10618,18 +10876,18 @@ async function runPlainList(opts, notices) {
|
|
|
10618
10876
|
}
|
|
10619
10877
|
}
|
|
10620
10878
|
if (rows.length === 0) {
|
|
10621
|
-
console.log(
|
|
10879
|
+
console.log(import_chalk33.default.dim(fuseTripped ? "Nothing found in the scanned span \u2014 older requests may still exist." : opts.errorsOnly ? "No errors logged yet." : "No requests logged yet."));
|
|
10622
10880
|
return;
|
|
10623
10881
|
}
|
|
10624
10882
|
const now = Date.now();
|
|
10625
10883
|
for (const d of groupIntoDays(rows)) {
|
|
10626
|
-
console.log(
|
|
10884
|
+
console.log(import_chalk33.default.bold(dayLabel(d.dayMs, now)));
|
|
10627
10885
|
for (const r of d.rows) console.log(` ${formatRow(r)}`);
|
|
10628
10886
|
console.log("");
|
|
10629
10887
|
}
|
|
10630
10888
|
const errs = rows.filter((r) => r.status >= 400).length;
|
|
10631
10889
|
const tail = fuseTripped ? " \xB7 stopped early (long empty span) \u2014 older requests may still exist" : more ? " \xB7 showing the most recent \u2014 `--json` streams the rest" : "";
|
|
10632
|
-
console.log(
|
|
10890
|
+
console.log(import_chalk33.default.dim(
|
|
10633
10891
|
`${rows.length} request${rows.length === 1 ? "" : "s"} \xB7 ${errs} error${errs === 1 ? "" : "s"}${tail}`
|
|
10634
10892
|
));
|
|
10635
10893
|
}
|
|
@@ -10648,7 +10906,7 @@ async function runHistoryBrowser(opts) {
|
|
|
10648
10906
|
for (const r of page.rows) console.log(JSON.stringify(r));
|
|
10649
10907
|
any = any || page.rows.length > 0;
|
|
10650
10908
|
if (page.fuseTripped || !page.nextCursor) {
|
|
10651
|
-
if (!any && !page.fuseTripped) console.error(
|
|
10909
|
+
if (!any && !page.fuseTripped) console.error(import_chalk33.default.dim(opts.errorsOnly ? "No errors logged yet." : "No requests logged yet."));
|
|
10652
10910
|
return;
|
|
10653
10911
|
}
|
|
10654
10912
|
cursor2 = page.nextCursor;
|
|
@@ -10669,7 +10927,7 @@ async function runHistoryBrowser(opts) {
|
|
|
10669
10927
|
scanRows = first.rows;
|
|
10670
10928
|
scanCursor = first.nextCursor;
|
|
10671
10929
|
if (scanRows.length === 0) {
|
|
10672
|
-
console.log(
|
|
10930
|
+
console.log(import_chalk33.default.dim(first.fuseTripped ? "Nothing found in the scanned span \u2014 older rows may exist." : opts.errorsOnly ? "No errors logged yet." : "No requests logged yet."));
|
|
10673
10931
|
return;
|
|
10674
10932
|
}
|
|
10675
10933
|
}
|
|
@@ -10680,10 +10938,10 @@ async function runHistoryBrowser(opts) {
|
|
|
10680
10938
|
const now2 = Date.now();
|
|
10681
10939
|
const oldestKey = days[days.length - 1]?.key;
|
|
10682
10940
|
const choices = days.map((d) => ({
|
|
10683
|
-
name: `${dayLabel(d.dayMs, now2).padEnd(12)} ${String(d.rows.length).padStart(4)}${(scanCursor !== void 0 || scanFloorUnknown) && d.key === oldestKey ? "+" : ""} requests${d.errors ?
|
|
10941
|
+
name: `${dayLabel(d.dayMs, now2).padEnd(12)} ${String(d.rows.length).padStart(4)}${(scanCursor !== void 0 || scanFloorUnknown) && d.key === oldestKey ? "+" : ""} requests${d.errors ? import_chalk33.default.red(` ${d.errors} errors`) : ""}`,
|
|
10684
10942
|
value: d.key
|
|
10685
10943
|
}));
|
|
10686
|
-
if (scanCursor) choices.push({ name:
|
|
10944
|
+
if (scanCursor) choices.push({ name: import_chalk33.default.dim("\u2026 older days"), value: "__older__" });
|
|
10687
10945
|
const { chosenDay } = await inquirer3.prompt([{
|
|
10688
10946
|
type: "list",
|
|
10689
10947
|
name: "chosenDay",
|
|
@@ -10703,7 +10961,7 @@ async function runHistoryBrowser(opts) {
|
|
|
10703
10961
|
scanCursor = void 0;
|
|
10704
10962
|
} else {
|
|
10705
10963
|
scanCursor = more.nextCursor;
|
|
10706
|
-
if (more.rows.length === 0 && !scanCursor) console.log(
|
|
10964
|
+
if (more.rows.length === 0 && !scanCursor) console.log(import_chalk33.default.dim("That was everything \u2014 no older requests."));
|
|
10707
10965
|
}
|
|
10708
10966
|
continue;
|
|
10709
10967
|
}
|
|
@@ -10725,11 +10983,11 @@ async function runHistoryBrowser(opts) {
|
|
|
10725
10983
|
cursor
|
|
10726
10984
|
}, notices);
|
|
10727
10985
|
if (page.rows.length === 0 && pageRows.length === 0) {
|
|
10728
|
-
console.log(
|
|
10986
|
+
console.log(import_chalk33.default.dim(page.fuseTripped ? "Stopped scanning this day (long empty span) \u2014 older rows in it may exist." : "No requests for this day."));
|
|
10729
10987
|
return;
|
|
10730
10988
|
}
|
|
10731
10989
|
if (page.rows.length === 0) {
|
|
10732
|
-
console.log(
|
|
10990
|
+
console.log(import_chalk33.default.dim(page.fuseTripped ? "Stopped scanning further into this day (long empty span) \u2014 older rows in it may exist." : "That was everything for this day."));
|
|
10733
10991
|
moreCursor = void 0;
|
|
10734
10992
|
} else {
|
|
10735
10993
|
pageRows = page.rows;
|
|
@@ -10740,8 +10998,8 @@ async function runHistoryBrowser(opts) {
|
|
|
10740
10998
|
name: formatRow(r),
|
|
10741
10999
|
value: r.request_id
|
|
10742
11000
|
}));
|
|
10743
|
-
if (moreCursor) choices.push({ name:
|
|
10744
|
-
choices.push({ name:
|
|
11001
|
+
if (moreCursor) choices.push({ name: import_chalk33.default.dim("\u2026 load more"), value: "__more__" });
|
|
11002
|
+
choices.push({ name: import_chalk33.default.dim("quit"), value: "__quit__" });
|
|
10745
11003
|
for (; ; ) {
|
|
10746
11004
|
const { picked } = await inquirer3.prompt([{
|
|
10747
11005
|
type: "list",
|
|
@@ -10776,19 +11034,19 @@ function wrapCells(items, width) {
|
|
|
10776
11034
|
${PANE_INDENT}`);
|
|
10777
11035
|
}
|
|
10778
11036
|
function renderHeaders(h) {
|
|
10779
|
-
if (!h) return
|
|
11037
|
+
if (!h) return import_chalk33.default.dim("\u2014");
|
|
10780
11038
|
const width = process.stdout.columns || 100;
|
|
10781
11039
|
if (h.values) {
|
|
10782
11040
|
const e = Object.entries(h.values);
|
|
10783
|
-
if (!e.length) return
|
|
11041
|
+
if (!e.length) return import_chalk33.default.dim("none");
|
|
10784
11042
|
const rank = (k) => {
|
|
10785
11043
|
const i = HEADER_PRIORITY.indexOf(k.toLowerCase());
|
|
10786
11044
|
return i === -1 ? NOISE_HEADER.test(k) ? 999 : 500 : i;
|
|
10787
11045
|
};
|
|
10788
|
-
return e.sort((a, b) => rank(a[0]) - rank(b[0])).map(([k, v]) => `${
|
|
11046
|
+
return e.sort((a, b) => rank(a[0]) - rank(b[0])).map(([k, v]) => `${import_chalk33.default.dim(k + ":")} ${v}`).join(`
|
|
10789
11047
|
${PANE_INDENT}`);
|
|
10790
11048
|
}
|
|
10791
|
-
if (!h.names?.length) return
|
|
11049
|
+
if (!h.names?.length) return import_chalk33.default.dim("none");
|
|
10792
11050
|
const names = [...h.names];
|
|
10793
11051
|
const noise = names.filter((n) => NOISE_HEADER.test(n));
|
|
10794
11052
|
const shown = names.filter((n) => !NOISE_HEADER.test(n)).sort((a, b) => {
|
|
@@ -10796,77 +11054,77 @@ ${PANE_INDENT}`);
|
|
|
10796
11054
|
const ib = HEADER_PRIORITY.indexOf(b.toLowerCase());
|
|
10797
11055
|
return (ia === -1 ? 500 : ia) - (ib === -1 ? 500 : ib) || a.localeCompare(b);
|
|
10798
11056
|
});
|
|
10799
|
-
const body = shown.length ? wrapCells(shown, width) :
|
|
11057
|
+
const body = shown.length ? wrapCells(shown, width) : import_chalk33.default.dim("(all forwarding headers)");
|
|
10800
11058
|
const tail = noise.length ? `
|
|
10801
|
-
${PANE_INDENT}${
|
|
10802
|
-
return `${body}${tail} ${
|
|
11059
|
+
${PANE_INDENT}${import_chalk33.default.dim(`+ ${noise.length} forwarding/client-hint header${noise.length === 1 ? "" : "s"} (cf-*, sec-*)`)}` : "";
|
|
11060
|
+
return `${body}${tail} ${import_chalk33.default.dim("(names only)")}`;
|
|
10803
11061
|
}
|
|
10804
11062
|
function renderBody(b) {
|
|
10805
|
-
if (!b) return
|
|
11063
|
+
if (!b) return import_chalk33.default.dim("\u2014");
|
|
10806
11064
|
const meta = [b.content_type, b.size != null ? `${b.size}B` : null, b.truncated ? "truncated" : null].filter(Boolean).join(" \xB7 ");
|
|
10807
|
-
if (b.not_captured) return
|
|
11065
|
+
if (b.not_captured) return import_chalk33.default.dim(`not captured (${b.not_captured})${meta ? " \xB7 " + meta : ""}`);
|
|
10808
11066
|
const isShape = b.value === void 0 && b.shape !== void 0;
|
|
10809
11067
|
const content = b.value !== void 0 ? b.value : b.shape;
|
|
10810
11068
|
if (content === null || content === void 0 || content === "" || content === "empty") {
|
|
10811
|
-
return
|
|
11069
|
+
return import_chalk33.default.dim(`empty${meta ? " \xB7 " + meta : ""}`);
|
|
10812
11070
|
}
|
|
10813
11071
|
const text = typeof content === "string" ? content : JSON.stringify(content, null, 2);
|
|
10814
|
-
const head = isShape ?
|
|
11072
|
+
const head = isShape ? import_chalk33.default.dim("shape only (values not captured)\n ") : "";
|
|
10815
11073
|
return head + text.split("\n").join("\n ");
|
|
10816
11074
|
}
|
|
10817
11075
|
function pane(title, tone, view, empty) {
|
|
10818
11076
|
console.log(` ${tone(title)}`);
|
|
10819
11077
|
if (empty !== void 0) {
|
|
10820
|
-
console.log(` ${
|
|
11078
|
+
console.log(` ${import_chalk33.default.dim(empty)}`);
|
|
10821
11079
|
return;
|
|
10822
11080
|
}
|
|
10823
11081
|
const v = view ?? {};
|
|
10824
|
-
if (v.url) console.log(` ${
|
|
11082
|
+
if (v.url) console.log(` ${import_chalk33.default.dim("target")} ${v.url}`);
|
|
10825
11083
|
if (v.status != null) {
|
|
10826
|
-
console.log(` ${
|
|
11084
|
+
console.log(` ${import_chalk33.default.dim("status")} ${statusColor(v.status)(String(v.status))}${v.status_text ? import_chalk33.default.dim(" " + v.status_text) : ""}`);
|
|
10827
11085
|
}
|
|
10828
|
-
if (v.raw_path) console.log(` ${
|
|
11086
|
+
if (v.raw_path) console.log(` ${import_chalk33.default.dim("path")} ${v.raw_path}`);
|
|
10829
11087
|
if (v.query && Object.keys(v.query).length) {
|
|
10830
|
-
console.log(` ${
|
|
11088
|
+
console.log(` ${import_chalk33.default.dim("query")} ${Object.entries(v.query).map(([k, q]) => `${k}=${q}`).join(" ")}`);
|
|
10831
11089
|
}
|
|
10832
|
-
if (v.jwt !== void 0) console.log(` ${
|
|
10833
|
-
console.log(` ${
|
|
10834
|
-
console.log(` ${
|
|
11090
|
+
if (v.jwt !== void 0) console.log(` ${import_chalk33.default.dim("jwt")} ${JSON.stringify(v.jwt)}`);
|
|
11091
|
+
console.log(` ${import_chalk33.default.dim("headers")} ${renderHeaders(v.headers)}`);
|
|
11092
|
+
console.log(` ${import_chalk33.default.dim("body")} ${renderBody(v.body)}`);
|
|
10835
11093
|
}
|
|
10836
11094
|
function printDetail(r, mode = "producer") {
|
|
10837
11095
|
const v = r.views;
|
|
10838
11096
|
console.log("");
|
|
10839
|
-
console.log(
|
|
11097
|
+
console.log(import_chalk33.default.bold(`${r.method} ${r.route}`) + " " + statusColor(r.status)(String(r.status)) + import_chalk33.default.dim(` ${r.duration_ms}ms`));
|
|
10840
11098
|
const cost = typeof r.cost_cents === "number" && r.cost_cents > 0 ? ` \xB7 ${r.cost_cents}\xA2` : "";
|
|
10841
|
-
console.log(
|
|
11099
|
+
console.log(import_chalk33.default.dim(` request_id ${r.request_id}${r.environment ? ` \xB7 env ${r.environment}` : ""}${r.consumer_id ? ` \xB7 consumer ${r.consumer_id}` : ""}${cost}`));
|
|
10842
11100
|
console.log("");
|
|
10843
11101
|
if (!v) {
|
|
10844
|
-
console.log(
|
|
11102
|
+
console.log(import_chalk33.default.dim(" No view detail captured for this request.\n"));
|
|
10845
11103
|
return;
|
|
10846
11104
|
}
|
|
10847
|
-
pane("Request (before) \u2014 client \u2192 APIblaze",
|
|
11105
|
+
pane("Request (before) \u2014 client \u2192 APIblaze", import_chalk33.default.cyan, v.request);
|
|
10848
11106
|
if (mode === "producer") {
|
|
10849
11107
|
pane(
|
|
10850
11108
|
"Request (after) \u2014 APIblaze \u2192 upstream",
|
|
10851
|
-
|
|
11109
|
+
import_chalk33.default.cyan,
|
|
10852
11110
|
v.forwarded_request,
|
|
10853
11111
|
v.forwarded_request ? void 0 : "Request did not reach an upstream."
|
|
10854
11112
|
);
|
|
10855
11113
|
pane(
|
|
10856
11114
|
"Response (before) \u2014 upstream \u2192 APIblaze",
|
|
10857
|
-
|
|
11115
|
+
import_chalk33.default.magenta,
|
|
10858
11116
|
v.upstream_response,
|
|
10859
11117
|
v.upstream_response ? void 0 : "No upstream response."
|
|
10860
11118
|
);
|
|
10861
11119
|
}
|
|
10862
|
-
pane("Response (after) \u2014 APIblaze \u2192 client",
|
|
11120
|
+
pane("Response (after) \u2014 APIblaze \u2192 client", import_chalk33.default.magenta, v.response);
|
|
10863
11121
|
console.log("");
|
|
10864
11122
|
}
|
|
10865
|
-
var
|
|
11123
|
+
var import_chalk33, readline2, ROUTE_W, WindowNotices, MAX_RECONNECTS, PING_MS, LIVENESS_MS, STABLE_MS, SEEN_CAP, TAIL_INSPECT_BUFFER, TAIL_INSPECT_CHOICES, SCAN_LIMIT, PAGE_LIMIT2, JSON_PAGE_LIMIT, MAX_EMPTY_HOPS, epipeGuarded, PLAIN_MAX_ROWS, NOISE_HEADER, HEADER_PRIORITY, PANE_INDENT;
|
|
10866
11124
|
var init_log_view = __esm({
|
|
10867
11125
|
"src/lib/log-view.ts"() {
|
|
10868
11126
|
"use strict";
|
|
10869
|
-
|
|
11127
|
+
import_chalk33 = __toESM(require("chalk"));
|
|
10870
11128
|
readline2 = __toESM(require("readline"));
|
|
10871
11129
|
ROUTE_W = 34;
|
|
10872
11130
|
WindowNotices = class {
|
|
@@ -10878,15 +11136,15 @@ var init_log_view = __esm({
|
|
|
10878
11136
|
note(res) {
|
|
10879
11137
|
if (res.history_unavailable && !this.saidUnavailable) {
|
|
10880
11138
|
this.saidUnavailable = true;
|
|
10881
|
-
console.error(
|
|
11139
|
+
console.error(import_chalk33.default.yellow("\u26A0 log history is unreachable right now \u2014 showing the live buffer only, older requests exist but cannot be read."));
|
|
10882
11140
|
}
|
|
10883
11141
|
if ((res.history_markers?.length ?? 0) > 0 && !this.saidGap) {
|
|
10884
11142
|
this.saidGap = true;
|
|
10885
|
-
console.error(
|
|
11143
|
+
console.error(import_chalk33.default.yellow("\u26A0 this range includes a volume-ceiling gap \u2014 some rows were dropped at intake and are gone; counts are incomplete."));
|
|
10886
11144
|
}
|
|
10887
11145
|
if ((res.dropped_this_hour ?? 0) > 0 && !this.saidDropped) {
|
|
10888
11146
|
this.saidDropped = true;
|
|
10889
|
-
console.error(
|
|
11147
|
+
console.error(import_chalk33.default.yellow(`\u26A0 volume ceiling \u2014 ${res.dropped_this_hour} rows dropped this hour; errors keep logging.`));
|
|
10890
11148
|
}
|
|
10891
11149
|
}
|
|
10892
11150
|
};
|
|
@@ -10973,7 +11231,7 @@ async function resolve(projectArg, versionArg, opts) {
|
|
|
10973
11231
|
return { projectId, projectName: match.projectName, version: version2, tenant };
|
|
10974
11232
|
}
|
|
10975
11233
|
function otherPlaneHint() {
|
|
10976
|
-
if (loadConsumer()) console.log(
|
|
11234
|
+
if (loadConsumer()) console.log(import_chalk34.default.dim(" \u2039consumer view: apiblaze consumer logs\u203A"));
|
|
10977
11235
|
}
|
|
10978
11236
|
function fetchWindow(projectId, version2, tenant) {
|
|
10979
11237
|
return (params) => {
|
|
@@ -10992,8 +11250,8 @@ function fetchWindow(projectId, version2, tenant) {
|
|
|
10992
11250
|
async function runLogsTail(projectArg, versionArg, opts) {
|
|
10993
11251
|
const { projectId, projectName, version: version2, tenant } = await resolve(projectArg, versionArg, opts);
|
|
10994
11252
|
if (!opts.json) {
|
|
10995
|
-
console.log(
|
|
10996
|
-
console.log(
|
|
11253
|
+
console.log(import_chalk34.default.bold("API Producer logs") + import_chalk34.default.dim(` \xB7 ${projectName}@${version2} \xB7 tenant ${tenant}${opts.errorsOnly ? " \xB7 errors only" : ""}`));
|
|
11254
|
+
console.log(import_chalk34.default.dim("every caller's requests, with cost and upstream detail"));
|
|
10997
11255
|
otherPlaneHint();
|
|
10998
11256
|
}
|
|
10999
11257
|
await runTailSession({
|
|
@@ -11014,8 +11272,8 @@ async function runLogsTail(projectArg, versionArg, opts) {
|
|
|
11014
11272
|
async function runLogsList(projectArg, versionArg, opts) {
|
|
11015
11273
|
const { projectId, projectName, version: version2, tenant } = await resolve(projectArg, versionArg, opts);
|
|
11016
11274
|
if (!opts.json) {
|
|
11017
|
-
console.log(
|
|
11018
|
-
console.log(
|
|
11275
|
+
console.log(import_chalk34.default.bold("API Producer logs") + import_chalk34.default.dim(` \xB7 ${projectName}@${version2} \xB7 tenant ${tenant}${opts.errorsOnly ? " \xB7 errors only" : ""}`));
|
|
11276
|
+
console.log(import_chalk34.default.dim("every caller's requests, with cost and upstream detail"));
|
|
11019
11277
|
otherPlaneHint();
|
|
11020
11278
|
console.log("");
|
|
11021
11279
|
}
|
|
@@ -11027,11 +11285,11 @@ async function runLogsList(projectArg, versionArg, opts) {
|
|
|
11027
11285
|
detail: "producer"
|
|
11028
11286
|
});
|
|
11029
11287
|
}
|
|
11030
|
-
var
|
|
11288
|
+
var import_chalk34, activeCaller, viaCaller;
|
|
11031
11289
|
var init_logs = __esm({
|
|
11032
11290
|
"src/commands/logs.ts"() {
|
|
11033
11291
|
"use strict";
|
|
11034
|
-
|
|
11292
|
+
import_chalk34 = __toESM(require("chalk"));
|
|
11035
11293
|
init_caller();
|
|
11036
11294
|
init_auth();
|
|
11037
11295
|
init_consumer_auth();
|
|
@@ -11312,12 +11570,12 @@ async function waitThrough(apiUrl, serverKey) {
|
|
|
11312
11570
|
async function confirmStep(opts, question) {
|
|
11313
11571
|
if (opts.yes || !interactive()) return true;
|
|
11314
11572
|
console.log();
|
|
11315
|
-
return ask(`${
|
|
11573
|
+
return ask(`${import_chalk35.default.bold("?")} ${question} ${import_chalk35.default.dim("(Y/n)")} `);
|
|
11316
11574
|
}
|
|
11317
11575
|
async function busy(text, fn, minMs = 0) {
|
|
11318
11576
|
if (!process.stdout.isTTY) return fn();
|
|
11319
11577
|
const t = Date.now();
|
|
11320
|
-
const spinner = (0,
|
|
11578
|
+
const spinner = (0, import_ora13.default)({ text: import_chalk35.default.dim(text), indent: 3, discardStdin: false }).start();
|
|
11321
11579
|
try {
|
|
11322
11580
|
const out = await fn();
|
|
11323
11581
|
const left = minMs - (Date.now() - t);
|
|
@@ -11350,7 +11608,7 @@ async function offerBrowserAndLogs(dir, opts) {
|
|
|
11350
11608
|
console.log();
|
|
11351
11609
|
if (await ask(` Open ${url} in your browser? (Y/n) `)) openBrowser3(url);
|
|
11352
11610
|
if (await ask(` Show the requests reaching ${B.product} here, live? (Y/n) `)) {
|
|
11353
|
-
console.log(
|
|
11611
|
+
console.log(import_chalk35.default.dim(` $ ${logsCommand(env)}`));
|
|
11354
11612
|
const { runLogsTail: runLogsTail2 } = await Promise.resolve().then(() => (init_logs(), logs_exports));
|
|
11355
11613
|
await runLogsTail2(env.APIBLAZE_NAME, void 0, { tenant: env.APIBLAZE_TENANT });
|
|
11356
11614
|
}
|
|
@@ -11387,7 +11645,7 @@ async function runDemo(opts = {}) {
|
|
|
11387
11645
|
const e = err instanceof DemoError ? err : new DemoError(`The demo could not start (${err.message}).`, `Run again: ${LATEST} demo`);
|
|
11388
11646
|
countRun({ event: `demo-failed-${e.why}` });
|
|
11389
11647
|
await stopDemos().catch(() => 0);
|
|
11390
|
-
console.log(`${
|
|
11648
|
+
console.log(`${import_chalk35.default.red("\u2717")} ${e.message}`);
|
|
11391
11649
|
console.log(` ${e.fix}`);
|
|
11392
11650
|
process.exit(1);
|
|
11393
11651
|
}
|
|
@@ -11398,11 +11656,11 @@ function titleCard(lines) {
|
|
|
11398
11656
|
const left = Math.floor((inner - t.length) / 2);
|
|
11399
11657
|
return " ".repeat(left) + t + " ".repeat(inner - t.length - left);
|
|
11400
11658
|
};
|
|
11401
|
-
const edge =
|
|
11659
|
+
const edge = import_chalk35.default.magenta;
|
|
11402
11660
|
console.log();
|
|
11403
11661
|
console.log(` ${edge(`\u256D${"\u2500".repeat(inner)}\u256E`)}`);
|
|
11404
11662
|
console.log(` ${edge("\u2502")}${" ".repeat(inner)}${edge("\u2502")}`);
|
|
11405
|
-
lines.forEach((l, i) => console.log(` ${edge("\u2502")}${i === 0 ?
|
|
11663
|
+
lines.forEach((l, i) => console.log(` ${edge("\u2502")}${i === 0 ? import_chalk35.default.bold(center(l)) : import_chalk35.default.dim(center(l))}${edge("\u2502")}`));
|
|
11406
11664
|
console.log(` ${edge("\u2502")}${" ".repeat(inner)}${edge("\u2502")}`);
|
|
11407
11665
|
console.log(` ${edge(`\u2570${"\u2500".repeat(inner)}\u256F`)}`);
|
|
11408
11666
|
}
|
|
@@ -11422,15 +11680,15 @@ function printTable(rows) {
|
|
|
11422
11680
|
if (line) out.push(line);
|
|
11423
11681
|
return out;
|
|
11424
11682
|
};
|
|
11425
|
-
const head = (l, r) => console.log(` ${
|
|
11683
|
+
const head = (l, r) => console.log(` ${import_chalk35.default.dim(l.padEnd(leftW))} ${import_chalk35.default.dim(r)}`);
|
|
11426
11684
|
head(`What ${B.product} set up`, "The command that does this");
|
|
11427
11685
|
head("\u2500".repeat(leftW), "\u2500".repeat(Math.min(rightW, 60)));
|
|
11428
11686
|
for (const r of rows) {
|
|
11429
|
-
const left = `${
|
|
11687
|
+
const left = `${import_chalk35.default.green("\u2713")} ${import_chalk35.default.bold(r.label.padEnd(17))} ${r.what}`;
|
|
11430
11688
|
const leftPlain = `\u2713 ${r.label.padEnd(17)} ${r.what}`;
|
|
11431
11689
|
const right = r.cmds.length ? r.cmds.flatMap(wrap2) : ["\u2014"];
|
|
11432
|
-
console.log(` ${left}${" ".repeat(Math.max(0, leftW - leftPlain.length))} ${
|
|
11433
|
-
for (const extra of right.slice(1)) console.log(` ${" ".repeat(leftW)} ${
|
|
11690
|
+
console.log(` ${left}${" ".repeat(Math.max(0, leftW - leftPlain.length))} ${import_chalk35.default.cyan(right[0])}`);
|
|
11691
|
+
for (const extra of right.slice(1)) console.log(` ${" ".repeat(leftW)} ${import_chalk35.default.cyan(extra)}`);
|
|
11434
11692
|
}
|
|
11435
11693
|
}
|
|
11436
11694
|
async function startDemo(opts) {
|
|
@@ -11439,8 +11697,8 @@ async function startDemo(opts) {
|
|
|
11439
11697
|
const dir = import_fs11.default.existsSync(import_path9.default.join(here, STATE_DIR, "state.json")) ? here : import_path9.default.resolve(here, DIR_NAME);
|
|
11440
11698
|
const rel = import_path9.default.relative(here, dir) || ".";
|
|
11441
11699
|
const cdDir = rel === "." ? "" : `cd ${rel} && `;
|
|
11442
|
-
const shell = (c, note = "") => console.log(` ${
|
|
11443
|
-
const ok = (text, note = "") => console.log(` ${
|
|
11700
|
+
const shell = (c, note = "") => console.log(` ${import_chalk35.default.dim("$")} ${import_chalk35.default.cyan(c)}${note ? ` ${import_chalk35.default.dim(note)}` : ""}`);
|
|
11701
|
+
const ok = (text, note = "") => console.log(` ${import_chalk35.default.green("\u2713")} ${text}${note ? ` ${import_chalk35.default.dim(note)}` : ""}`);
|
|
11444
11702
|
const stopHere = (step, lines) => {
|
|
11445
11703
|
console.log();
|
|
11446
11704
|
for (const l of lines) console.log(` ${l}`);
|
|
@@ -11466,14 +11724,14 @@ paths:`));
|
|
|
11466
11724
|
const L = (c) => `${LATEST} ${c}`;
|
|
11467
11725
|
writeEnv(dir, { BACKEND_SECRET: backendSecret, DEMO_NINO_TENANT: ninoTenant, APP_PORT: appPort, BACKEND_PORT: backendPort2, DIRECT_PORT: directPort });
|
|
11468
11726
|
console.log();
|
|
11469
|
-
console.log(
|
|
11727
|
+
console.log(import_chalk35.default.bold("1. The app: ResiResi, a table booking app for Tenants (Nino and Gino Pizzas) to allow end-users to book tables in their restaurants") + import_chalk35.default.dim(` (no ${B.product} yet)`));
|
|
11470
11728
|
console.log(` Written to ${rel}. Its routes, from openapi.yaml:`);
|
|
11471
|
-
for (const r of routesOf(openapi)) console.log(` ${r.method.padEnd(7)}${r.path.padEnd(21)}${
|
|
11472
|
-
console.log(
|
|
11729
|
+
for (const r of routesOf(openapi)) console.log(` ${r.method.padEnd(7)}${r.path.padEnd(21)}${import_chalk35.default.dim(r.summary)}`);
|
|
11730
|
+
console.log(import_chalk35.default.dim(" No login, no API keys, no permission checks in its code. It only checks one shared backend secret (in .env.local)."));
|
|
11473
11731
|
if (!await confirmStep(opts, `Ok to serve the backend on localhost:${backendPort2}?`)) {
|
|
11474
11732
|
return stopHere("backend", [
|
|
11475
11733
|
`Stopped before starting anything. ResiResi is written to ${rel}.`,
|
|
11476
|
-
`Start it any time: ${cdDir}npm run start ${
|
|
11734
|
+
`Start it any time: ${cdDir}npm run start ${import_chalk35.default.dim("\xB7")} or run the whole demo again: ${LATEST} demo`
|
|
11477
11735
|
]);
|
|
11478
11736
|
}
|
|
11479
11737
|
shell(`${cdDir}npm run start`, "npm run stop \xB7 npm run restart");
|
|
@@ -11481,23 +11739,23 @@ paths:`));
|
|
|
11481
11739
|
registerDemo(dir);
|
|
11482
11740
|
const appUp = await busy("Starting the reservation backend and its front-end\u2026", () => waitPorts([backendPort2, directPort]), 1800);
|
|
11483
11741
|
if (!appUp) throw new DemoError(`The ResiResi app did not start (see ${import_path9.default.join(rel, STATE_DIR, "app.log")}).`, `Run again: ${LATEST} demo`, "app-not-up");
|
|
11484
|
-
ok(`${
|
|
11485
|
-
ok(`${
|
|
11742
|
+
ok(`${import_chalk35.default.bold("Reservation backend")}: localhost:${backendPort2}`, "unprotected");
|
|
11743
|
+
ok(`${import_chalk35.default.bold("Unprotected reservation front-end")}, going directly to the reservation backend: ${import_chalk35.default.bold(`http://localhost:${directPort}`)}`);
|
|
11486
11744
|
console.log(" Book a table directly, the way the unprotected front-end does:");
|
|
11487
|
-
console.log(
|
|
11488
|
-
console.log(
|
|
11489
|
-
console.log(
|
|
11745
|
+
console.log(import_chalk35.default.cyan(` curl -X POST http://localhost:${backendPort2}/reservations \\`));
|
|
11746
|
+
console.log(import_chalk35.default.cyan(` -H "x-target-api-key: $(grep BACKEND_SECRET ${import_path9.default.join(rel, ".env.local")} | cut -d= -f2)" \\`));
|
|
11747
|
+
console.log(import_chalk35.default.cyan(` -H "x-abz-user-id: ana" -H "x-abz-tenant-id: ${ninoTenant}" \\`));
|
|
11490
11748
|
const tomorrow = new Date(Date.now() + 864e5);
|
|
11491
11749
|
const day = `${tomorrow.getFullYear()}-${String(tomorrow.getMonth() + 1).padStart(2, "0")}-${String(tomorrow.getDate()).padStart(2, "0")}`;
|
|
11492
|
-
console.log(
|
|
11750
|
+
console.log(import_chalk35.default.cyan(` -H "content-type: application/json" -d '{"date":"${day}","time":"19:30","guests":2}'`));
|
|
11493
11751
|
if (!await confirmStep(opts, `Ok to put an ${B.product} proxy and MCP in front of the reservation backend?`)) {
|
|
11494
11752
|
return stopHere("proxy", [
|
|
11495
|
-
`Stopped before ${B.product}. ResiResi keeps running on this laptop: http://localhost:${directPort} ${
|
|
11753
|
+
`Stopped before ${B.product}. ResiResi keeps running on this laptop: http://localhost:${directPort} ${import_chalk35.default.dim(`(${cdDir}npm run stop stops it)`)}`,
|
|
11496
11754
|
`Run the whole demo again any time: ${LATEST} demo`
|
|
11497
11755
|
]);
|
|
11498
11756
|
}
|
|
11499
11757
|
console.log();
|
|
11500
|
-
console.log(
|
|
11758
|
+
console.log(import_chalk35.default.bold(`2. ${B.product}, in front of it`));
|
|
11501
11759
|
const tCreate = Date.now();
|
|
11502
11760
|
const setup = await busy(`Creating the proxy, its tenants, admins, rules and limits\u2026`, async () => {
|
|
11503
11761
|
const r = await createProxy2(name, backendPort2, openapi, ninoTenant);
|
|
@@ -11603,13 +11861,13 @@ paths:`));
|
|
|
11603
11861
|
]);
|
|
11604
11862
|
}
|
|
11605
11863
|
console.log();
|
|
11606
|
-
console.log(
|
|
11864
|
+
console.log(import_chalk35.default.bold("3. Live on the internet"));
|
|
11607
11865
|
shell(`${LATEST} dev --port ${backendPort2} --project ${project} --auto`, "the tunnel, in the background");
|
|
11608
11866
|
await busy(`Opening the tunnel to localhost:${backendPort2} and checking it with a real call\u2026`, async () => {
|
|
11609
11867
|
await spawnTunnel(dir, state);
|
|
11610
11868
|
await waitThrough(apiUrl, serverKey);
|
|
11611
11869
|
}, 1200);
|
|
11612
|
-
ok(`${
|
|
11870
|
+
ok(`${import_chalk35.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`);
|
|
11613
11871
|
printAddresses(dir);
|
|
11614
11872
|
if (!await confirmStep(opts, "Ok to launch the protected website, which will use the proxy to reach the backend?")) {
|
|
11615
11873
|
return stopHere("website", [
|
|
@@ -11618,7 +11876,7 @@ paths:`));
|
|
|
11618
11876
|
]);
|
|
11619
11877
|
}
|
|
11620
11878
|
console.log();
|
|
11621
|
-
console.log(
|
|
11879
|
+
console.log(import_chalk35.default.bold(`4. The protected website, through ${B.product}`));
|
|
11622
11880
|
shell(`${cdDir}npm run restart`, "picks up the keys in .env.local");
|
|
11623
11881
|
const siteUp = await busy("Starting the protected website\u2026", async () => {
|
|
11624
11882
|
appCtl(dir, "--stop");
|
|
@@ -11626,16 +11884,16 @@ paths:`));
|
|
|
11626
11884
|
return waitPorts([appPort, directPort, backendPort2]);
|
|
11627
11885
|
}, 1500);
|
|
11628
11886
|
if (!siteUp) throw new DemoError(`The protected website did not start (see ${import_path9.default.join(rel, STATE_DIR, "app.log")}).`, `Try: ${cdDir}npm run restart`, "site-not-up");
|
|
11629
|
-
ok(`${
|
|
11630
|
-
ok(`${
|
|
11887
|
+
ok(`${import_chalk35.default.bold("Protected reservation website")}, going through the ${B.product} proxy ${apiUrl}: ${import_chalk35.default.bold(`http://localhost:${appPort}`)}`);
|
|
11888
|
+
ok(`${import_chalk35.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");
|
|
11631
11889
|
console.log();
|
|
11632
|
-
console.log(
|
|
11890
|
+
console.log(import_chalk35.default.dim(` Logs: ${logsCommand(readEnvFile(import_path9.default.join(dir, ".env.local")))} \xB7 Stop the tunnel: ${LATEST} demo stop \xB7 Your own app: ${LATEST} dev --port <port>`));
|
|
11633
11891
|
await offerBrowserAndLogs(dir, opts);
|
|
11634
11892
|
return "demo-ready";
|
|
11635
11893
|
}
|
|
11636
11894
|
function printAddresses(dir) {
|
|
11637
11895
|
const env = readEnvFile(import_path9.default.join(dir, ".env.local"));
|
|
11638
|
-
const row = (label3, value, note = "") => console.log(` ${label3.padEnd(11)} ${value}${note ? ` ${
|
|
11896
|
+
const row = (label3, value, note = "") => console.log(` ${label3.padEnd(11)} ${value}${note ? ` ${import_chalk35.default.dim(note)}` : ""}`);
|
|
11639
11897
|
row("Public API", env.APIBLAZE_URL ?? "", `${B.product} tunnel \u2192 localhost:${env.BACKEND_PORT}`);
|
|
11640
11898
|
row("MCP", env.APIBLAZE_MCP_URL ?? "", "for Claude, ChatGPT, Cursor (Nino Pizza)");
|
|
11641
11899
|
if (env.APIBLAZE_PORTAL_URL) row("Dev portal", env.APIBLAZE_PORTAL_URL, "sign in, get a key, try the API");
|
|
@@ -11654,7 +11912,7 @@ async function runDemoStart(opts = {}) {
|
|
|
11654
11912
|
const state = loadState(dir);
|
|
11655
11913
|
const env = readEnvFile(import_path9.default.join(dir, ".env.local"));
|
|
11656
11914
|
if (!state || !env.APIBLAZE_SERVER_KEY) {
|
|
11657
|
-
console.log(`${
|
|
11915
|
+
console.log(`${import_chalk35.default.red("\u2717")} There is no demo here to start.`);
|
|
11658
11916
|
console.log(` Make one: ${LATEST} demo`);
|
|
11659
11917
|
process.exit(1);
|
|
11660
11918
|
}
|
|
@@ -11664,31 +11922,31 @@ async function runDemoStart(opts = {}) {
|
|
|
11664
11922
|
} catch (err) {
|
|
11665
11923
|
const e = err instanceof DemoError ? err : new DemoError(`The tunnel could not start (${err.message}).`, `Start over: ${LATEST} demo`);
|
|
11666
11924
|
countRun({ event: `demo-restart-failed-${e.why}` });
|
|
11667
|
-
console.log(`${
|
|
11925
|
+
console.log(`${import_chalk35.default.red("\u2717")} ${e.message}`);
|
|
11668
11926
|
console.log(` ${e.fix}`);
|
|
11669
11927
|
process.exit(1);
|
|
11670
11928
|
}
|
|
11671
11929
|
countRun({ event: "demo-restarted" });
|
|
11672
|
-
console.log(`${
|
|
11930
|
+
console.log(`${import_chalk35.default.green("\u2713")} Tunnel open: the public address reaches localhost:${env.BACKEND_PORT} again ${import_chalk35.default.dim(`(${Math.round((Date.now() - t0) / 1e3)} s)`)}`);
|
|
11673
11931
|
printAddresses(dir);
|
|
11674
11932
|
const appUp = await fetch(`http://127.0.0.1:${env.APP_PORT}/api/config`).then(() => true).catch(() => false);
|
|
11675
|
-
if (!appUp) console.log(
|
|
11933
|
+
if (!appUp) console.log(import_chalk35.default.yellow(` ResiResi is not running: cd ${import_path9.default.relative(process.cwd(), dir) || "."} && npm run start`));
|
|
11676
11934
|
await offerBrowserAndLogs(dir, opts);
|
|
11677
11935
|
}
|
|
11678
11936
|
async function runDemoLogs() {
|
|
11679
11937
|
const env = readEnvFile(import_path9.default.join(demoDir(), ".env.local"));
|
|
11680
11938
|
if (!env.APIBLAZE_NAME) {
|
|
11681
|
-
console.log(`${
|
|
11939
|
+
console.log(`${import_chalk35.default.red("\u2717")} There is no demo here.`);
|
|
11682
11940
|
console.log(` Make one: ${LATEST} demo`);
|
|
11683
11941
|
process.exit(1);
|
|
11684
11942
|
}
|
|
11685
|
-
console.log(
|
|
11943
|
+
console.log(import_chalk35.default.dim(`$ ${logsCommand(env)}`));
|
|
11686
11944
|
const { runLogsTail: runLogsTail2 } = await Promise.resolve().then(() => (init_logs(), logs_exports));
|
|
11687
11945
|
await runLogsTail2(env.APIBLAZE_NAME, void 0, { tenant: env.APIBLAZE_TENANT });
|
|
11688
11946
|
}
|
|
11689
11947
|
async function runDemoStop() {
|
|
11690
11948
|
const n = await stopTunnels();
|
|
11691
|
-
console.log(n ? `${
|
|
11949
|
+
console.log(n ? `${import_chalk35.default.green("\u2713")} Stopped the tunnel. ResiResi still runs on this laptop (npm run stop stops it).` : "No demo tunnel is running.");
|
|
11692
11950
|
}
|
|
11693
11951
|
async function serveTunnel(dir) {
|
|
11694
11952
|
const state = loadState(dir);
|
|
@@ -11746,7 +12004,7 @@ async function serveTunnel(dir) {
|
|
|
11746
12004
|
await new Promise(() => {
|
|
11747
12005
|
});
|
|
11748
12006
|
}
|
|
11749
|
-
var import_fs11, import_path9, import_net2, import_child_process7, import_crypto,
|
|
12007
|
+
var import_fs11, import_path9, import_net2, import_child_process7, import_crypto, import_chalk35, import_ora13, DIR_NAME, STATE_DIR, LATEST, DEMO_RPS, DEMO_ADMIN_EMAIL, DEMO_GINO_ADMIN_EMAIL, RULE_SENTENCE, DemoError, ADJ, NOUN, pick2, registryFile, alive, interactive, logsCommand;
|
|
11750
12008
|
var init_demo = __esm({
|
|
11751
12009
|
"src/commands/demo.ts"() {
|
|
11752
12010
|
"use strict";
|
|
@@ -11755,8 +12013,8 @@ var init_demo = __esm({
|
|
|
11755
12013
|
import_net2 = __toESM(require("net"));
|
|
11756
12014
|
import_child_process7 = require("child_process");
|
|
11757
12015
|
import_crypto = __toESM(require("crypto"));
|
|
11758
|
-
|
|
11759
|
-
|
|
12016
|
+
import_chalk35 = __toESM(require("chalk"));
|
|
12017
|
+
import_ora13 = __toESM(require("ora"));
|
|
11760
12018
|
init_api();
|
|
11761
12019
|
init_tunnel_client();
|
|
11762
12020
|
init_auth();
|
|
@@ -12849,8 +13107,8 @@ Not ready: ${lines.length} blocker${lines.length === 1 ? "" : "s"} (${r.rendered
|
|
|
12849
13107
|
// src/commands/connector.ts
|
|
12850
13108
|
var import_fs9 = __toESM(require("fs"));
|
|
12851
13109
|
var import_path7 = __toESM(require("path"));
|
|
12852
|
-
var
|
|
12853
|
-
var
|
|
13110
|
+
var import_chalk32 = __toESM(require("chalk"));
|
|
13111
|
+
var import_ora12 = __toESM(require("ora"));
|
|
12854
13112
|
init_caller();
|
|
12855
13113
|
init_directory();
|
|
12856
13114
|
init_brand();
|
|
@@ -13152,14 +13410,14 @@ async function promptForMissing(r, interactive2, defaults = { prompts: [] }, pla
|
|
|
13152
13410
|
if (testPrompt) {
|
|
13153
13411
|
let res = await testPrompt(text);
|
|
13154
13412
|
if (!res.called) {
|
|
13155
|
-
say_(
|
|
13413
|
+
say_(import_chalk32.default.yellow(` \u2013 No tool call for "${text}". A prompt should say what the tool does (a real URL, name or id helps).`));
|
|
13156
13414
|
const again = await inquirer3.prompt([{ type: "input", name: "line", message: "Reword it (Enter keeps it):", default: text, validate: withinLimit(a) }]);
|
|
13157
13415
|
if (again.line?.trim() && again.line.trim() !== text) {
|
|
13158
13416
|
text = again.line.trim();
|
|
13159
13417
|
res = await testPrompt(text);
|
|
13160
13418
|
}
|
|
13161
13419
|
}
|
|
13162
|
-
if (!res.called) say_(
|
|
13420
|
+
if (!res.called) say_(import_chalk32.default.yellow(" \u2013 still no tool call; kept (the report will say so)"));
|
|
13163
13421
|
}
|
|
13164
13422
|
lines.push(text);
|
|
13165
13423
|
}
|
|
@@ -13167,7 +13425,7 @@ async function promptForMissing(r, interactive2, defaults = { prompts: [] }, pla
|
|
|
13167
13425
|
out.prompt = lines;
|
|
13168
13426
|
if (save) {
|
|
13169
13427
|
const why = await save({ prompt: lines });
|
|
13170
|
-
if (why) say_(
|
|
13428
|
+
if (why) say_(import_chalk32.default.yellow(` not saved: ${why}`));
|
|
13171
13429
|
}
|
|
13172
13430
|
}
|
|
13173
13431
|
continue;
|
|
@@ -13190,7 +13448,7 @@ async function promptForMissing(r, interactive2, defaults = { prompts: [] }, pla
|
|
|
13190
13448
|
if (save) {
|
|
13191
13449
|
const why = await save({ [a.key]: value.trim() });
|
|
13192
13450
|
if (why) {
|
|
13193
|
-
say_(
|
|
13451
|
+
say_(import_chalk32.default.yellow(` not saved: ${why}`));
|
|
13194
13452
|
delete out[a.key];
|
|
13195
13453
|
}
|
|
13196
13454
|
}
|
|
@@ -13223,7 +13481,7 @@ async function askOpenMode(caller, proj2, r, opts, interactive2, say) {
|
|
|
13223
13481
|
const { open } = await inquirer3.prompt([{ type: "confirm", name: "open", message: OPEN_MODE_QUESTION, default: false }]);
|
|
13224
13482
|
if (!open) return false;
|
|
13225
13483
|
await patchProject(caller, proj2, { directory_metadata: { auth_mode: "open" } }, "Open mode: no sign-in on the connector");
|
|
13226
|
-
say(
|
|
13484
|
+
say(import_chalk32.default.green(" \u2713 open mode: the connector takes no sign-in (public data); no reviewer account is needed."));
|
|
13227
13485
|
return true;
|
|
13228
13486
|
}
|
|
13229
13487
|
async function promptListingBasics(r, interactive2, defaults, given, platforms = PLATFORMS, save) {
|
|
@@ -13251,7 +13509,7 @@ async function promptListingBasics(r, interactive2, defaults, given, platforms =
|
|
|
13251
13509
|
if (save) {
|
|
13252
13510
|
const why = await save({ [w.key]: value.trim() });
|
|
13253
13511
|
if (why) {
|
|
13254
|
-
say_(
|
|
13512
|
+
say_(import_chalk32.default.yellow(` not saved: ${why}`));
|
|
13255
13513
|
delete out[w.key];
|
|
13256
13514
|
}
|
|
13257
13515
|
}
|
|
@@ -13262,7 +13520,7 @@ async function promptListingBasics(r, interactive2, defaults, given, platforms =
|
|
|
13262
13520
|
async function createFromSpec(spec, opts, say) {
|
|
13263
13521
|
const { runCreate: runCreate2 } = await Promise.resolve().then(() => (init_create(), create_exports));
|
|
13264
13522
|
let created = null;
|
|
13265
|
-
say(
|
|
13523
|
+
say(import_chalk32.default.bold(`Creating a directory-ready ${B.thingShort} from ${spec}\u2026`));
|
|
13266
13524
|
await runCreate2({
|
|
13267
13525
|
openapi: spec,
|
|
13268
13526
|
directoryReady: true,
|
|
@@ -13334,20 +13592,20 @@ async function ensureMcpDomain(caller, proj2, r, opts, defaults, interactive2, s
|
|
|
13334
13592
|
}
|
|
13335
13593
|
}
|
|
13336
13594
|
if (!hostname2) throw new Error("A custom MCP hostname is required for the directories (--domain mcp.<your domain>).");
|
|
13337
|
-
const sp2 = opts.json ? null : (0,
|
|
13595
|
+
const sp2 = opts.json ? null : (0, import_ora12.default)(`Registering ${hostname2}\u2026`).start();
|
|
13338
13596
|
row = await producer(caller, { method: "POST", path: base2, body: { hostname: hostname2, api_version: proj2.apiVersion, kind: "mcp" }, summary: `Add custom MCP domain ${hostname2}` });
|
|
13339
13597
|
sp2?.succeed(`Registered ${row.hostname ?? hostname2} as the MCP server's domain.`);
|
|
13340
13598
|
}
|
|
13341
13599
|
const hostname = row.hostname ?? opts.domain ?? "";
|
|
13342
13600
|
if (row.status === "active") return { active: true, hostname, id: row.id };
|
|
13343
13601
|
say("");
|
|
13344
|
-
say(
|
|
13345
|
-
say(` ${
|
|
13346
|
-
if (row.dcv) say(` ${
|
|
13602
|
+
say(import_chalk32.default.bold(" Add this DNS record at your DNS provider (one record; nothing else to add):"));
|
|
13603
|
+
say(` ${import_chalk32.default.cyan("CNAME")} ${row.cnameRecord?.name ?? hostname} \u2192 ${row.cnameRecord?.value ?? "proxy.abz.run"}`);
|
|
13604
|
+
if (row.dcv) say(` ${import_chalk32.default.cyan("TXT")} ${row.dcv.name} = ${row.dcv.value}`);
|
|
13347
13605
|
say("");
|
|
13348
13606
|
const minutes = opts.wait === void 0 ? 15 : Number(opts.wait);
|
|
13349
13607
|
if (!(minutes > 0) || !row.id) return { active: false, hostname, id: row.id };
|
|
13350
|
-
const sp = opts.json ? null : (0,
|
|
13608
|
+
const sp = opts.json ? null : (0, import_ora12.default)(`Waiting for ${hostname} to activate (Cloudflare validates it once the CNAME resolves; up to ${minutes} min, Ctrl-C to stop and re-run later)\u2026`).start();
|
|
13351
13609
|
const until = Date.now() + minutes * 6e4;
|
|
13352
13610
|
let status2 = row.status ?? "pending";
|
|
13353
13611
|
while (Date.now() < until) {
|
|
@@ -13364,6 +13622,93 @@ async function ensureMcpDomain(caller, proj2, r, opts, defaults, interactive2, s
|
|
|
13364
13622
|
sp?.fail(`${hostname} is not active yet (${status2}).`);
|
|
13365
13623
|
return { active: false, hostname, id: row.id };
|
|
13366
13624
|
}
|
|
13625
|
+
var HOSTED_RULE = "UPSTREAM_HOSTED";
|
|
13626
|
+
var PROTECTED_RULE = "UPSTREAM_PROTECTED";
|
|
13627
|
+
function validateProductionUrl(input) {
|
|
13628
|
+
const v = (input ?? "").trim();
|
|
13629
|
+
if (!/^https:\/\//i.test(v)) return "It must start with https://";
|
|
13630
|
+
let host = "";
|
|
13631
|
+
try {
|
|
13632
|
+
host = new URL(v).hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
13633
|
+
} catch {
|
|
13634
|
+
return `${v} is not a URL.`;
|
|
13635
|
+
}
|
|
13636
|
+
const privateHost = host === "localhost" || host === "::1" || /\.(localhost|local|internal)$/.test(host) || /^(127\.|10\.|192\.168\.|169\.254\.|0\.)/.test(host) || /^172\.(1[6-9]|2\d|3[01])\./.test(host);
|
|
13637
|
+
return privateHost ? `${host} is this machine or a private network; reviewers cannot reach it.` : true;
|
|
13638
|
+
}
|
|
13639
|
+
function parseUpstreamHeader(opts) {
|
|
13640
|
+
if (opts.upstreamHeader !== void 0 && opts.upstreamHeaderEnv !== void 0) throw new Error("Pass one of --upstream-header and --upstream-header-env.");
|
|
13641
|
+
if (opts.upstreamHeaderEnv !== void 0) {
|
|
13642
|
+
const i2 = opts.upstreamHeaderEnv.indexOf("=");
|
|
13643
|
+
const name2 = i2 > 0 ? opts.upstreamHeaderEnv.slice(0, i2).trim() : "";
|
|
13644
|
+
const variable = i2 > 0 ? opts.upstreamHeaderEnv.slice(i2 + 1).trim() : "";
|
|
13645
|
+
if (!name2 || !variable) throw new Error("--upstream-header-env wants NAME=ENVVAR.");
|
|
13646
|
+
const value2 = process.env[variable];
|
|
13647
|
+
if (!value2) throw new Error(`Environment variable ${variable} is empty or not set.`);
|
|
13648
|
+
return { name: name2, value: value2 };
|
|
13649
|
+
}
|
|
13650
|
+
if (opts.upstreamHeader === void 0) return null;
|
|
13651
|
+
const i = opts.upstreamHeader.indexOf(":");
|
|
13652
|
+
const name = i > 0 ? opts.upstreamHeader.slice(0, i).trim() : "";
|
|
13653
|
+
const value = i > 0 ? opts.upstreamHeader.slice(i + 1).trim() : "";
|
|
13654
|
+
if (!name || !value) throw new Error('--upstream-header wants "Name: value".');
|
|
13655
|
+
return { name, value };
|
|
13656
|
+
}
|
|
13657
|
+
async function ensureHostedUpstream(caller, teamId, proj2, r, opts, interactive2, say, refetch) {
|
|
13658
|
+
const failing = (x, id) => x.report.results.some((y) => y.id === id && !y.pass);
|
|
13659
|
+
const shape = (x) => x.target ?? {};
|
|
13660
|
+
let cur = r;
|
|
13661
|
+
const header = parseUpstreamHeader(opts);
|
|
13662
|
+
if (header && opts.upstreamOpen) throw new Error("Pass one of --upstream-header and --upstream-open.");
|
|
13663
|
+
if (failing(cur, HOSTED_RULE)) {
|
|
13664
|
+
let url = opts.targetUrl?.trim();
|
|
13665
|
+
if (url) {
|
|
13666
|
+
const ok = validateProductionUrl(url);
|
|
13667
|
+
if (ok !== true) throw new Error(`--target-url: ${ok}`);
|
|
13668
|
+
} else if (interactive2) {
|
|
13669
|
+
say(" A listing has to stay up when your laptop sleeps, so reviewers and users need the hosted app, not this machine.");
|
|
13670
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
13671
|
+
const { value } = await inquirer3.prompt([{ type: "input", name: "value", message: "Where does this run in production? (https URL; Enter to skip for now)", validate: (v) => !(v ?? "").trim() ? true : validateProductionUrl(v) }]);
|
|
13672
|
+
url = value?.trim();
|
|
13673
|
+
}
|
|
13674
|
+
if (url) {
|
|
13675
|
+
const env = cur.report.project.environment;
|
|
13676
|
+
const { environmentsWithTarget: environmentsWithTarget2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
13677
|
+
const environments = await environmentsWithTarget2(caller, teamId, proj2, env, url);
|
|
13678
|
+
await patchProject(caller, proj2, { environments }, `Point ${env} at ${url}`);
|
|
13679
|
+
say(import_chalk32.default.green(` \u2713 ${env} now forwards to ${url}`));
|
|
13680
|
+
cur = await refetch();
|
|
13681
|
+
} else {
|
|
13682
|
+
say(import_chalk32.default.dim(` Skipped: still not hosted, so this cannot be submitted yet. Everything else goes on; ${B.cli} connector ${proj2.projectName} --target-url <url> finishes it.`));
|
|
13683
|
+
}
|
|
13684
|
+
} else if (opts.targetUrl) {
|
|
13685
|
+
say(import_chalk32.default.dim(` --target-url ignored: this already forwards to a hosted server (${shape(cur).hosts?.upstreamTarget ?? "see the scorecard"}).`));
|
|
13686
|
+
}
|
|
13687
|
+
const skipped = shape(cur).metadata?.skippedRules ?? [];
|
|
13688
|
+
if (failing(cur, PROTECTED_RULE) && !skipped.includes(PROTECTED_RULE)) {
|
|
13689
|
+
let chosen = header;
|
|
13690
|
+
let open = !!opts.upstreamOpen;
|
|
13691
|
+
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
|
+
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)` }]);
|
|
13695
|
+
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." }]);
|
|
13697
|
+
chosen = { name: name.trim(), value };
|
|
13698
|
+
} else open = true;
|
|
13699
|
+
}
|
|
13700
|
+
if (chosen) {
|
|
13701
|
+
const { setUpstreamHeader: setUpstreamHeader2 } = await Promise.resolve().then(() => (init_transform(), transform_exports));
|
|
13702
|
+
await setUpstreamHeader2(caller, proj2, chosen.name, chosen.value);
|
|
13703
|
+
say(import_chalk32.default.green(` \u2713 every forwarded call now carries ${chosen.name} (stored encrypted)`));
|
|
13704
|
+
cur = await refetch();
|
|
13705
|
+
} else if (open) {
|
|
13706
|
+
await patchProject(caller, proj2, { directory_metadata: { skipped_rules: [.../* @__PURE__ */ new Set([...skipped, PROTECTED_RULE])] } }, "The backend checks its own credentials");
|
|
13707
|
+
cur = await refetch();
|
|
13708
|
+
}
|
|
13709
|
+
}
|
|
13710
|
+
return cur;
|
|
13711
|
+
}
|
|
13367
13712
|
function generatePassword(bytes = () => crypto.getRandomValues(new Uint8Array(20))) {
|
|
13368
13713
|
const alphabet = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
13369
13714
|
return Array.from(bytes(), (b) => alphabet[b % alphabet.length]).join("");
|
|
@@ -13399,12 +13744,12 @@ async function ensureReviewerAccount(caller, teamId, proj2, opts = {}) {
|
|
|
13399
13744
|
return { username: finalUser, password, tenant: proj2.tenant };
|
|
13400
13745
|
}
|
|
13401
13746
|
function printReviewer(acct, proj2) {
|
|
13402
|
-
console.log(
|
|
13747
|
+
console.log(import_chalk32.default.green(`Reviewer account ${import_chalk32.default.bold(acct.username)} on tenant ${acct.tenant} \u2014 stored (encrypted) for the submission packet.`));
|
|
13403
13748
|
console.log();
|
|
13404
|
-
console.log(
|
|
13405
|
-
console.log(` ${
|
|
13749
|
+
console.log(import_chalk32.default.bold(" Password (shown once):"));
|
|
13750
|
+
console.log(` ${import_chalk32.default.cyan(acct.username)} ${import_chalk32.default.bgWhite.black(` ${acct.password} `)}`);
|
|
13406
13751
|
console.log();
|
|
13407
|
-
console.log(
|
|
13752
|
+
console.log(import_chalk32.default.dim(` Seed realistic data through the ${B.thingShort} (billed per turn): ${B.cli} agent --seed-for ${proj2.projectName}`));
|
|
13408
13753
|
}
|
|
13409
13754
|
async function runConnectorReviewer(project, opts) {
|
|
13410
13755
|
const caller = requireCaller();
|
|
@@ -13446,7 +13791,7 @@ async function interruptConnector(ctx) {
|
|
|
13446
13791
|
const sum = summaryOf(r);
|
|
13447
13792
|
if (!ctx.json) {
|
|
13448
13793
|
console.log("");
|
|
13449
|
-
console.log(
|
|
13794
|
+
console.log(import_chalk32.default.bold(`Saved.${sum ? ` ${sum}.` : ""}`) + ` Pick up any time: ${import_chalk32.default.cyan(`${B.cli} connector ${ctx.proj.projectId}`)} or ${import_chalk32.default.cyan(pageLinkOf(r, ctx.proj))}`);
|
|
13450
13795
|
}
|
|
13451
13796
|
process.exit(0);
|
|
13452
13797
|
}
|
|
@@ -13478,7 +13823,7 @@ async function runConnector(target2, opts) {
|
|
|
13478
13823
|
const created = await createFromSpec(specSource, opts, say);
|
|
13479
13824
|
projectName = created.name;
|
|
13480
13825
|
version2 = created.version;
|
|
13481
|
-
say(
|
|
13826
|
+
say(import_chalk32.default.green(` ${B.thing} ${import_chalk32.default.bold(created.name)} created.`));
|
|
13482
13827
|
}
|
|
13483
13828
|
const proj2 = await resolveProjectVia(caller, teamId, projectName, version2);
|
|
13484
13829
|
const flagBody = directoryBodyFromFlags(opts);
|
|
@@ -13490,7 +13835,7 @@ async function runConnector(target2, opts) {
|
|
|
13490
13835
|
process.stdout.write(JSON.stringify({ attested: Object.keys(flagBody.directory_attestations), headline: r2.rendered.headline, summary: r2.progress?.summary ?? null }) + "\n");
|
|
13491
13836
|
return;
|
|
13492
13837
|
}
|
|
13493
|
-
say(
|
|
13838
|
+
say(import_chalk32.default.green(`Attested: ${Object.keys(flagBody.directory_attestations).join(", ")}.`) + import_chalk32.default.dim(` ${summaryOf(r2) ?? r2.rendered.headline}`));
|
|
13494
13839
|
return;
|
|
13495
13840
|
}
|
|
13496
13841
|
const explicit = compliantGiven(opts);
|
|
@@ -13516,36 +13861,36 @@ async function runConnector(target2, opts) {
|
|
|
13516
13861
|
const dirsDiffer = explicit && !!stored && (stored.length !== platforms.length || stored.some((p) => !platforms.includes(p)));
|
|
13517
13862
|
const needPatch = !modeOn || !!flagBody.directory_metadata || !!flagBody.directory_attestations || !!flagBody.openai_challenge_token || dirsDiffer;
|
|
13518
13863
|
if (needPatch) {
|
|
13519
|
-
const sp = json || modeOn ? null : (0,
|
|
13864
|
+
const sp = json || modeOn ? null : (0, import_ora12.default)(`Turning directory-ready mode on for ${proj2.projectName}\u2026`).start();
|
|
13520
13865
|
const meta = { ...flagBody.directory_metadata ?? {}, ...!modeOn || dirsDiffer || !stored ? { directories: platforms } : {} };
|
|
13521
13866
|
await patchProject(caller, proj2, { directory_ready: true, ...flagBody, directory_metadata: meta }, modeOn ? "Save the flags" : "Enable directory-ready mode");
|
|
13522
13867
|
sp?.succeed(`Directory-ready mode is on for ${proj2.projectName} v${proj2.apiVersion}.`);
|
|
13523
|
-
if (dirsDiffer) say(
|
|
13868
|
+
if (dirsDiffer) say(import_chalk32.default.dim(` directories: ${platforms.map((p) => p === "claude" ? "Claude" : "ChatGPT").join(" + ")} (was ${stored.map((p) => p === "claude" ? "Claude" : "ChatGPT").join(" + ")})`));
|
|
13524
13869
|
r = await fetchDirectoryReport(caller, proj2, reportOpts());
|
|
13525
13870
|
}
|
|
13526
13871
|
const link = pageLinkOf(r, proj2);
|
|
13527
13872
|
const sum = summaryOf(r);
|
|
13528
13873
|
if (sum) {
|
|
13529
|
-
say(`${
|
|
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)}`);
|
|
13530
13875
|
const done = doneLine(r);
|
|
13531
|
-
if (done) say(
|
|
13876
|
+
if (done) say(import_chalk32.default.dim(` ${done}`));
|
|
13532
13877
|
} else {
|
|
13533
|
-
say(
|
|
13878
|
+
say(import_chalk32.default.dim(` ${r.rendered.headline}`));
|
|
13534
13879
|
}
|
|
13535
13880
|
const framed = /* @__PURE__ */ new Set();
|
|
13536
13881
|
const frame = (id) => {
|
|
13537
13882
|
const st = stepOf(r, id);
|
|
13538
13883
|
if (!st || framed.has(id)) return;
|
|
13539
13884
|
framed.add(id);
|
|
13540
|
-
say(
|
|
13885
|
+
say(import_chalk32.default.bold(`\u2500\u2500 ${st.n} ${st.title} (${openCount(st)} left) \u2500\u2500`));
|
|
13541
13886
|
};
|
|
13542
13887
|
const closed = (id) => {
|
|
13543
13888
|
const st = stepOf(r, id);
|
|
13544
|
-
if (st && framed.has(id) && st.status === "done") say(
|
|
13889
|
+
if (st && framed.has(id) && st.status === "done") say(import_chalk32.default.green(` \u2713 ${st.n} ${st.title}`));
|
|
13545
13890
|
};
|
|
13546
13891
|
const formHint = (id) => {
|
|
13547
13892
|
const st = stepOf(r, id);
|
|
13548
|
-
if (st && framed.has(id) && interactive2) say(
|
|
13893
|
+
if (st && framed.has(id) && interactive2) say(import_chalk32.default.dim(` Prefer a form? ${pageLinkOf(r, proj2, st.web)} has the same fields.`));
|
|
13549
13894
|
};
|
|
13550
13895
|
const open = (id) => stepOpen(stepOf(r, id));
|
|
13551
13896
|
const runnable = (id) => stepRunnable(stepOf(r, id));
|
|
@@ -13560,7 +13905,7 @@ async function runConnector(target2, opts) {
|
|
|
13560
13905
|
if (isApiError(e, 404)) return { applied: [], skipped: [{ fix: "all", reason: "server has no auto-fixes yet" }] };
|
|
13561
13906
|
throw e;
|
|
13562
13907
|
});
|
|
13563
|
-
for (const f2 of fixes.applied ?? []) say(
|
|
13908
|
+
for (const f2 of fixes.applied ?? []) say(import_chalk32.default.green(` \u2713 fixed: ${f2}`));
|
|
13564
13909
|
if (fixes.applied?.length) r = await fetchDirectoryReport(caller, proj2, reportOpts());
|
|
13565
13910
|
}
|
|
13566
13911
|
let defaults = { prompts: [] };
|
|
@@ -13577,6 +13922,7 @@ async function runConnector(target2, opts) {
|
|
|
13577
13922
|
let domain = { active: true };
|
|
13578
13923
|
if (wantStep("address") && open("address")) {
|
|
13579
13924
|
frame("address");
|
|
13925
|
+
r = await ensureHostedUpstream(caller, teamId, proj2, r, opts, interactive2, say, () => fetchDirectoryReport(caller, proj2, reportOpts()));
|
|
13580
13926
|
const domainWasBlocking = r.report.results.some((x) => x.id === DOMAIN_RULE && !x.pass);
|
|
13581
13927
|
domain = await ensureMcpDomain(caller, proj2, r, { ...opts, wait: 0 }, defaults, interactive2, say);
|
|
13582
13928
|
if (domainWasBlocking && domain.active) r = await fetchDirectoryReport(caller, proj2, reportOpts());
|
|
@@ -13630,10 +13976,10 @@ async function runConnector(target2, opts) {
|
|
|
13630
13976
|
}
|
|
13631
13977
|
if (!tester) return { called: true, tools: [] };
|
|
13632
13978
|
const t = tester;
|
|
13633
|
-
say(
|
|
13979
|
+
say(import_chalk32.default.dim(` trying it through ${t.clients.map((c) => c === "claude" ? "Claude Code" : "Codex CLI").join(" and ")}\u2026`));
|
|
13634
13980
|
const res = await t.test(p);
|
|
13635
13981
|
if (res.tools.length) await patchProject(caller, proj2, { directory_metadata: { prompt_tools: { [p.trim()]: res.tools } } }, "Remember which tool the prompt called").catch(() => null);
|
|
13636
|
-
for (const [client, rr] of Object.entries(res.byClient)) say(rr.called ?
|
|
13982
|
+
for (const [client, rr] of Object.entries(res.byClient)) say(rr.called ? import_chalk32.default.green(` \u2713 ${client === "claude" ? "Claude" : "Codex"} called ${rr.tools.join(", ")}`) : import_chalk32.default.yellow(` \u2013 ${client === "claude" ? "Claude" : "Codex"} called no tool`));
|
|
13637
13983
|
return res;
|
|
13638
13984
|
};
|
|
13639
13985
|
const askSteps = ["listing", "attestations", "token"].filter((id) => wantStep(id) && runnable(id));
|
|
@@ -13687,11 +14033,11 @@ async function runConnector(target2, opts) {
|
|
|
13687
14033
|
const { runConnectorExercise: runConnectorExercise2 } = await Promise.resolve().then(() => (init_connector_exercise(), connector_exercise_exports));
|
|
13688
14034
|
const ran = await runConnectorExercise2(proj2.projectId, { team: opts.team ?? proj2.teamId, apiversion: opts.apiversion, json, yes: opts.yes, noEmail: opts.email === false, report: false, platforms: [...platforms], provenPromptNotes, seed: opts.seed });
|
|
13689
14035
|
if (interactive2 && ran.failedPrompts.length) {
|
|
13690
|
-
for (const [p, clients] of Object.entries(ran.failedByClient ?? {})) say(
|
|
14036
|
+
for (const [p, clients] of Object.entries(ran.failedByClient ?? {})) say(import_chalk32.default.yellow(` \u2013 "${p.slice(0, 60)}": no tool call in ${clients.map((c) => c === "claude" ? "Claude Code" : "Codex CLI").join(" and ")}`));
|
|
13691
14037
|
const { rewritten, prompts } = await rewriteFailedPrompts(r, ran.failedPrompts, saveAnswer, say);
|
|
13692
14038
|
if (rewritten.length) {
|
|
13693
14039
|
const { clientsFor: clientsFor2 } = await Promise.resolve().then(() => (init_connector_exercise(), connector_exercise_exports));
|
|
13694
|
-
say(
|
|
14040
|
+
say(import_chalk32.default.dim(` Running the ${prompts.length} prompts through ${clientsFor2(platforms).map((c) => c === "claude" ? "Claude Code" : "Codex CLI").join(" and ")} again (the report reads the last run)\u2026`));
|
|
13695
14041
|
const again = await runConnectorExercise2(proj2.projectId, { team: opts.team ?? proj2.teamId, apiversion: opts.apiversion, json, noEmail: opts.email === false, report: false, platforms: [...platforms], promptsOnly: prompts, provenPromptNotes: ran.notes.filter((n) => n.kind === "negative") });
|
|
13696
14042
|
ran.promptTools = { ...ran.promptTools, ...again.promptTools };
|
|
13697
14043
|
ran.codexPromptTools = { ...ran.codexPromptTools, ...again.codexPromptTools };
|
|
@@ -13717,13 +14063,13 @@ async function runConnector(target2, opts) {
|
|
|
13717
14063
|
process.exit(isReady(r, platforms) ? 0 : 1);
|
|
13718
14064
|
}
|
|
13719
14065
|
say("");
|
|
13720
|
-
say(`${
|
|
14066
|
+
say(`${import_chalk32.default.bold(s2 ?? r.rendered.headline)} ${import_chalk32.default.dim(`\xB7 ${link}`)}`);
|
|
13721
14067
|
process.exit(isReady(r, platforms) ? 0 : 1);
|
|
13722
14068
|
}
|
|
13723
14069
|
const tokenOnly = readyExcept(r, platforms, [SNAPSHOT_RULE, TOKEN_RULE]) && r.report.results.some((x) => x.id === TOKEN_RULE && !x.pass);
|
|
13724
14070
|
if (!readyExceptSnapshot(r, platforms) && !tokenOnly) {
|
|
13725
14071
|
if (!domain.active && domain.hostname && !json) {
|
|
13726
|
-
console.error(
|
|
14072
|
+
console.error(import_chalk32.default.yellow(`
|
|
13727
14073
|
${domain.hostname} is not active yet. Once the CNAME is in place, run again: ${B.cli} connector ${proj2.projectId}`));
|
|
13728
14074
|
}
|
|
13729
14075
|
exitWithReport(r, proj2, platforms, json, interactive2, opts.verbose);
|
|
@@ -13732,7 +14078,7 @@ async function runConnector(target2, opts) {
|
|
|
13732
14078
|
let watching = false;
|
|
13733
14079
|
if (opts.watch !== false) {
|
|
13734
14080
|
watching = await producer(caller, { method: "POST", path: `${projectPath3(proj2)}/directory-watch`, body: { enabled: true }, summary: "Turn on the nightly re-check" }).then(() => true).catch((e) => {
|
|
13735
|
-
say(
|
|
14081
|
+
say(import_chalk32.default.dim(` (nightly re-check not switched on: ${e.message})`));
|
|
13736
14082
|
return false;
|
|
13737
14083
|
});
|
|
13738
14084
|
}
|
|
@@ -13750,7 +14096,7 @@ async function runConnector(target2, opts) {
|
|
|
13750
14096
|
const manifest = packet.json.chatgpt;
|
|
13751
14097
|
if (manifest) {
|
|
13752
14098
|
const written = await writePluginPackage2(dir, manifest);
|
|
13753
|
-
if (written.problem) say(
|
|
14099
|
+
if (written.problem) say(import_chalk32.default.yellow(` ${written.problem}`));
|
|
13754
14100
|
zipPath = written.zipPath;
|
|
13755
14101
|
if (written.seq) await patchProject(caller, proj2, { directory_metadata: { plugin_package_seq: written.seq } }, "Bump the plugin package version").catch(() => null);
|
|
13756
14102
|
}
|
|
@@ -13764,28 +14110,28 @@ async function runConnector(target2, opts) {
|
|
|
13764
14110
|
console.log();
|
|
13765
14111
|
const card = scorecardText(r);
|
|
13766
14112
|
if (opts.verbose && r.rendered.compliance) {
|
|
13767
|
-
console.log(r.rendered.compliance.split("\n").map((l, i) => i === 0 ?
|
|
14113
|
+
console.log(r.rendered.compliance.split("\n").map((l, i) => i === 0 ? import_chalk32.default.bold(l) : l.replace(/^(\s+)✓ /, (_m, sp) => `${sp}${import_chalk32.default.green("\u2713")} `).replace(/^(\s+)! /, (_m, sp) => `${sp}${import_chalk32.default.yellow("!")} `)).join("\n"));
|
|
13768
14114
|
} else if (card) {
|
|
13769
14115
|
console.log(card);
|
|
13770
14116
|
} else if (r.rendered.compliance) {
|
|
13771
|
-
console.log(r.rendered.compliance.split("\n").map((l, i) => i === 0 ?
|
|
14117
|
+
console.log(r.rendered.compliance.split("\n").map((l, i) => i === 0 ? import_chalk32.default.bold(l) : l.replace(/^(\s+)✓ /, (_m, sp) => `${sp}${import_chalk32.default.green("\u2713")} `).replace(/^(\s+)! /, (_m, sp) => `${sp}${import_chalk32.default.yellow("!")} `)).join("\n"));
|
|
13772
14118
|
} else {
|
|
13773
14119
|
printReport(r, false);
|
|
13774
14120
|
}
|
|
13775
14121
|
console.log();
|
|
13776
|
-
console.log(
|
|
13777
|
-
console.log(` Submission packet: ${
|
|
13778
|
-
OpenAI plugin package: ${
|
|
13779
|
-
console.log(
|
|
14122
|
+
console.log(import_chalk32.default.green.bold(` ${r.rendered.headline}`));
|
|
14123
|
+
console.log(` Submission packet: ${import_chalk32.default.cyan(mdPath)} and ${import_chalk32.default.cyan(jsonPath)}${zipPath ? `
|
|
14124
|
+
OpenAI plugin package: ${import_chalk32.default.cyan(zipPath)}` : ""}`);
|
|
14125
|
+
console.log(import_chalk32.default.dim(isOpenMode(r) ? " (open mode: no reviewer credentials in the packet; the connector takes no sign-in)" : " (the packet carries the reviewer credentials \u2014 keep it out of git)"));
|
|
13780
14126
|
printSubmitSteps(r, mdPath, platforms, watching);
|
|
13781
14127
|
if (tokenOnly) {
|
|
13782
14128
|
const tok = r.report.results.find((x) => x.id === TOKEN_RULE);
|
|
13783
|
-
console.log(
|
|
13784
|
-
console.log(
|
|
14129
|
+
console.log(import_chalk32.default.yellow(` One thing left for ChatGPT: ${withProject(tok?.fix ?? "paste the OpenAI domain token", proj2.projectId)}`));
|
|
14130
|
+
console.log(import_chalk32.default.dim(` Then tell us you submitted: ${B.cli} connector submitted ${proj2.projectId}`));
|
|
13785
14131
|
} else {
|
|
13786
|
-
console.log(
|
|
14132
|
+
console.log(import_chalk32.default.dim(` When you have submitted: ${B.cli} connector submitted ${proj2.projectId} \u2014 we re-check nightly and email you if anything drifts.`));
|
|
13787
14133
|
}
|
|
13788
|
-
console.log(
|
|
14134
|
+
console.log(import_chalk32.default.dim(` ${r.report.disclaimer}`));
|
|
13789
14135
|
if (!isReady(r, platforms)) process.exit(1);
|
|
13790
14136
|
} catch (e) {
|
|
13791
14137
|
if (interrupting) return;
|
|
@@ -13808,7 +14154,7 @@ async function waitForExternal(caller, proj2, r, platforms, domain, opts, intera
|
|
|
13808
14154
|
const sleep2 = deps.sleep ?? ((ms2) => new Promise((res) => setTimeout(res, ms2)));
|
|
13809
14155
|
const link = pageLinkOf(r, proj2, "#step-9");
|
|
13810
14156
|
say("");
|
|
13811
|
-
say(
|
|
14157
|
+
say(import_chalk32.default.bold(` Waiting for: ${[needDomain ? `your CNAME (${domain.hostname})` : null, needLogin ? "your Claude sign-in" : null].filter(Boolean).join(" \xB7 ")}`));
|
|
13812
14158
|
if (needLogin) {
|
|
13813
14159
|
const url = r.report.project.publishedUrl ?? "";
|
|
13814
14160
|
const name = r.target?.metadata?.serverName ?? proj2.projectId;
|
|
@@ -13816,19 +14162,19 @@ async function waitForExternal(caller, proj2, r, platforms, domain, opts, intera
|
|
|
13816
14162
|
say(" One sign-in from a real Claude client is the last proof. In another terminal:");
|
|
13817
14163
|
say(` claude mcp add --transport http ${alias} ${url}`);
|
|
13818
14164
|
say(` claude then /mcp \u2192 ${alias} \u2192 sign in with the reviewer account`);
|
|
13819
|
-
say(
|
|
13820
|
-
say(
|
|
14165
|
+
say(import_chalk32.default.dim(` or in claude.ai: Settings \u2192 Connectors \u2192 Add custom connector \u2192 ${url} (what a reviewer does).`));
|
|
14166
|
+
say(import_chalk32.default.dim(` Afterwards: claude mcp remove ${alias}`));
|
|
13821
14167
|
const { default: inquirer3 } = await import("inquirer");
|
|
13822
14168
|
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 }]);
|
|
13823
14169
|
if (!go) needLogin = false;
|
|
13824
14170
|
if (!needDomain && !needLogin) return r;
|
|
13825
14171
|
}
|
|
13826
|
-
say(
|
|
14172
|
+
say(import_chalk32.default.dim(` Or leave now: run ${B.cli} connector ${proj2.projectId} again any time, or watch it at ${link}`));
|
|
13827
14173
|
const base2 = `/projects/${encodeURIComponent(proj2.projectId)}/domains`;
|
|
13828
14174
|
const started = now();
|
|
13829
14175
|
const domainUntil = started + minutes * 6e4;
|
|
13830
14176
|
const loginUntil = started + (deps.maxLoginMs ?? 10 * 6e4);
|
|
13831
|
-
const sp = opts.json ? null : (0,
|
|
14177
|
+
const sp = opts.json ? null : (0, import_ora12.default)("Checking every 15 seconds\u2026").start();
|
|
13832
14178
|
let cur = r;
|
|
13833
14179
|
let status2 = "pending";
|
|
13834
14180
|
let activated = false;
|
|
@@ -13839,13 +14185,13 @@ async function waitForExternal(caller, proj2, r, platforms, domain, opts, intera
|
|
|
13839
14185
|
status2 = s?.status ?? status2;
|
|
13840
14186
|
if (status2 === "active") {
|
|
13841
14187
|
sp?.stop();
|
|
13842
|
-
say(
|
|
14188
|
+
say(import_chalk32.default.green(` \u2713 ${domain.hostname} is active.`));
|
|
13843
14189
|
sp?.start();
|
|
13844
14190
|
needDomain = false;
|
|
13845
14191
|
activated = true;
|
|
13846
14192
|
} else if (status2 === "suspended" || status2 === "missing" || now() >= domainUntil) {
|
|
13847
14193
|
sp?.stop();
|
|
13848
|
-
say(
|
|
14194
|
+
say(import_chalk32.default.yellow(` \u2013 ${domain.hostname} is not active yet (${status2}); run again once the CNAME is in place.`));
|
|
13849
14195
|
sp?.start();
|
|
13850
14196
|
needDomain = false;
|
|
13851
14197
|
}
|
|
@@ -13855,12 +14201,12 @@ async function waitForExternal(caller, proj2, r, platforms, domain, opts, intera
|
|
|
13855
14201
|
const nowRule = cur.report.results.find((x) => x.id === "OAUTH_FLOW_COMPLETED");
|
|
13856
14202
|
if (nowRule?.pass) {
|
|
13857
14203
|
sp?.stop();
|
|
13858
|
-
say(
|
|
14204
|
+
say(import_chalk32.default.green(` \u2713 ${nowRule.message ?? "a Claude client completed the sign-in"}`));
|
|
13859
14205
|
sp?.start();
|
|
13860
14206
|
needLogin = false;
|
|
13861
14207
|
} else if (now() >= loginUntil) {
|
|
13862
14208
|
sp?.stop();
|
|
13863
|
-
say(
|
|
14209
|
+
say(import_chalk32.default.yellow(" \u2013 no Claude sign-in seen in 10 minutes; run again after signing in."));
|
|
13864
14210
|
sp?.start();
|
|
13865
14211
|
needLogin = false;
|
|
13866
14212
|
}
|
|
@@ -13888,16 +14234,16 @@ async function runConnectorSubmitted(project, opts) {
|
|
|
13888
14234
|
writeJsonLine({ project: proj2.projectId, submitted: r.progress?.submitted ?? submitted, summary: r.progress?.summary ?? null, line: step?.line ?? null });
|
|
13889
14235
|
return;
|
|
13890
14236
|
}
|
|
13891
|
-
console.log(
|
|
14237
|
+
console.log(import_chalk32.default.green(` \u2713 ${opts.undo ? "forgotten" : "recorded"}: ${dirs.map((p) => p === "claude" ? "Claude" : "ChatGPT").join(" + ")}`));
|
|
13892
14238
|
if (step) console.log(` ${step.line}`);
|
|
13893
|
-
console.log(
|
|
14239
|
+
console.log(import_chalk32.default.dim(` ${summaryOf(r) ?? r.rendered.headline} \xB7 ${pageLinkOf(r, proj2)}`));
|
|
13894
14240
|
}
|
|
13895
14241
|
async function rewriteFailedPrompts(r, failed, save, say) {
|
|
13896
14242
|
const current = currentListingValue(r, "prompt");
|
|
13897
14243
|
const list = Array.isArray(current) ? [...current] : [];
|
|
13898
14244
|
const { default: inquirer3 } = await import("inquirer");
|
|
13899
14245
|
const rewritten = [];
|
|
13900
|
-
say(
|
|
14246
|
+
say(import_chalk32.default.yellow(` ${failed.length === 1 ? "This prompt" : `${failed.length} prompts`} produced no tool call; a prompt should name what the tool does${failed.length === 1 ? "" : ", one at a time"}:`));
|
|
13901
14247
|
for (const old of failed) {
|
|
13902
14248
|
const { value } = await inquirer3.prompt([{ type: "input", name: "value", message: `Rewrite "${old}" (Enter keeps it):`, default: old, validate: (v) => v.trim().length <= 300 ? true : `${v.trim().length} characters; 300 at most` }]);
|
|
13903
14249
|
const next = (value ?? "").trim();
|
|
@@ -13910,7 +14256,7 @@ async function rewriteFailedPrompts(r, failed, save, say) {
|
|
|
13910
14256
|
if (rewritten.length) {
|
|
13911
14257
|
const why = await save({ prompt: list });
|
|
13912
14258
|
if (why) {
|
|
13913
|
-
say_(
|
|
14259
|
+
say_(import_chalk32.default.yellow(` not saved: ${why}`));
|
|
13914
14260
|
return { rewritten: [], prompts: list };
|
|
13915
14261
|
}
|
|
13916
14262
|
}
|
|
@@ -13955,9 +14301,9 @@ async function offerDrafts(caller, proj2, r, ran, interactive2, say, platforms =
|
|
|
13955
14301
|
try {
|
|
13956
14302
|
await patchProject(caller, proj2, { directory_portal_answers: { chatgpt_test_cases: cases } }, "Save OpenAI's test cases");
|
|
13957
14303
|
saved = true;
|
|
13958
|
-
say(
|
|
14304
|
+
say(import_chalk32.default.green(` \u2713 OpenAI's positive test cases written from the Codex run: ${cases.positive.length}/5${cases.positive.length < 5 ? " (more prompts that call a tool are needed for five)" : ""}`));
|
|
13959
14305
|
} catch (e) {
|
|
13960
|
-
say(
|
|
14306
|
+
say(import_chalk32.default.yellow(` not saved: ${e instanceof Error ? e.message : String(e)}`));
|
|
13961
14307
|
}
|
|
13962
14308
|
}
|
|
13963
14309
|
}
|
|
@@ -13965,11 +14311,11 @@ async function offerDrafts(caller, proj2, r, ran, interactive2, say, platforms =
|
|
|
13965
14311
|
if (!drafts.length) return saved;
|
|
13966
14312
|
const inquirer3 = interactive2 ? (await import("inquirer")).default : null;
|
|
13967
14313
|
say("");
|
|
13968
|
-
say(
|
|
14314
|
+
say(import_chalk32.default.bold(` Written from the run \u2014 the portal's ${drafts.map((d) => d.label.toLowerCase()).join(", ")}:`));
|
|
13969
14315
|
for (const d of drafts) {
|
|
13970
14316
|
let text = d.draft;
|
|
13971
14317
|
say("");
|
|
13972
|
-
say(
|
|
14318
|
+
say(import_chalk32.default.bold(` ${d.label}`) + import_chalk32.default.dim(` (${d.draft.length} characters; the portal wants ${ANSWER_MIN2[d.key]}+)`));
|
|
13973
14319
|
for (const line of d.draft.split("\n")) say(` ${line}`);
|
|
13974
14320
|
if (inquirer3) {
|
|
13975
14321
|
const { ok } = await inquirer3.prompt([{ type: "confirm", name: "ok", message: `Use this ${d.label.toLowerCase()}? (n = type your own)`, default: true }]);
|
|
@@ -13982,9 +14328,9 @@ async function offerDrafts(caller, proj2, r, ran, interactive2, say, platforms =
|
|
|
13982
14328
|
try {
|
|
13983
14329
|
await patchProject(caller, proj2, body, `Save the ${d.label.toLowerCase()}`);
|
|
13984
14330
|
saved = true;
|
|
13985
|
-
say(
|
|
14331
|
+
say(import_chalk32.default.green(` \u2713 ${d.label.toLowerCase()} saved`));
|
|
13986
14332
|
} catch (e) {
|
|
13987
|
-
say(
|
|
14333
|
+
say(import_chalk32.default.yellow(` not saved: ${e instanceof Error ? e.message : String(e)}`));
|
|
13988
14334
|
}
|
|
13989
14335
|
}
|
|
13990
14336
|
return saved;
|
|
@@ -14006,7 +14352,7 @@ function exitWithReport(r, proj2, platforms, json, interactive2 = false, verbose
|
|
|
14006
14352
|
const blocking = r.report.results.filter((x) => !x.pass && x.severity === "block" && !x.unverified && x.platforms.some((p) => want.includes(p)));
|
|
14007
14353
|
const real = blocking.filter((x) => x.id !== SNAPSHOT_RULE);
|
|
14008
14354
|
const first = real.find((x) => x.tier !== "review") ?? real[0] ?? blocking[0];
|
|
14009
|
-
console.error(
|
|
14355
|
+
console.error(import_chalk32.default.red(`
|
|
14010
14356
|
Not ready: ${blocking.length} blocker${blocking.length === 1 ? "" : "s"} (${blocking.map((x) => x.id).join(", ")}).`));
|
|
14011
14357
|
const name = proj2.projectId;
|
|
14012
14358
|
if (!interactive2) {
|
|
@@ -14017,7 +14363,7 @@ Not ready: ${blocking.length} blocker${blocking.length === 1 ? "" : "s"} (${bloc
|
|
|
14017
14363
|
if (answerish.length && answerish.every((x) => !x.platforms.includes("claude"))) console.error(` Next: ${withProject(answerish[0].fix ?? answerish[0].message ?? answerish[0].title, name)}`);
|
|
14018
14364
|
else if (answerish.length) console.error(` Next: run \`${B.cli} connector ${name}${platforms.includes("chatgpt") ? ` --compliant ${platforms.length > 1 ? "all" : "chatgpt"}` : ""}\` in a terminal with the exercise on \u2014 it asks what only you can answer and writes the use cases, the reviewer instructions and the description from the run; or pass them: \`${B.cli} connector answers ${name} --use-cases \u2026 --data-handling own --health-data no --sponsored-content no\`.`);
|
|
14019
14365
|
else if (first) console.error(` Next: ${withProject(first.fix ?? first.message ?? first.title, name)}`);
|
|
14020
|
-
console.error(
|
|
14366
|
+
console.error(import_chalk32.default.dim(` Report page: ${pageLinkOf(r, proj2)}`));
|
|
14021
14367
|
process.exit(1);
|
|
14022
14368
|
}
|
|
14023
14369
|
function flagsForRules(ids, platforms) {
|
|
@@ -14055,6 +14401,12 @@ function flagsForRules(ids, platforms) {
|
|
|
14055
14401
|
case "REVIEWER_ACCOUNT_POPULATED":
|
|
14056
14402
|
add("--seed");
|
|
14057
14403
|
break;
|
|
14404
|
+
case HOSTED_RULE:
|
|
14405
|
+
add("--target-url <https://\u2026>");
|
|
14406
|
+
break;
|
|
14407
|
+
case PROTECTED_RULE:
|
|
14408
|
+
add('--upstream-header "<Name>: <value>" (or --upstream-open)');
|
|
14409
|
+
break;
|
|
14058
14410
|
case "API_OWNERSHIP_DOMAIN":
|
|
14059
14411
|
add('--data-handling own|proxied "<permission>"');
|
|
14060
14412
|
break;
|
|
@@ -14125,7 +14477,7 @@ async function confirmOpenWorld(proj2, environment, interactive2, flag, openMode
|
|
|
14125
14477
|
if (!unset.length) return false;
|
|
14126
14478
|
let answer = flag ? /^(y|yes|true|1)$/i.test(flag.trim()) : null;
|
|
14127
14479
|
if (answer === null && !interactive2) {
|
|
14128
|
-
say(
|
|
14480
|
+
say(import_chalk32.default.dim(` openWorldHint not answered for ${unset.length} tool${unset.length === 1 ? "" : "s"} (pass --open-world yes|no)`));
|
|
14129
14481
|
return false;
|
|
14130
14482
|
}
|
|
14131
14483
|
if (answer === null) {
|
|
@@ -14136,7 +14488,7 @@ async function confirmOpenWorld(proj2, environment, interactive2, flag, openMode
|
|
|
14136
14488
|
cat.overrides = { ...cat.overrides ?? {} };
|
|
14137
14489
|
for (const n of unset) cat.overrides[n] = { ...cat.overrides[n] ?? {}, openWorld: answer };
|
|
14138
14490
|
await saveCatalogue2(t, cat);
|
|
14139
|
-
say(
|
|
14491
|
+
say(import_chalk32.default.green(` \u2713 openWorldHint ${answer ? "true" : "false"} on ${unset.length === cat.tools.length ? "every tool" : unset.join(", ")}`));
|
|
14140
14492
|
return true;
|
|
14141
14493
|
}
|
|
14142
14494
|
async function fixToolDescriptions(proj2, environment, interactive2, say) {
|
|
@@ -14153,9 +14505,9 @@ async function fixToolDescriptions(proj2, environment, interactive2, say) {
|
|
|
14153
14505
|
const dropped = tools.filter(isUtilityTool).map((x) => x.name);
|
|
14154
14506
|
const todo = kept.filter(needsDescription);
|
|
14155
14507
|
if (!dropped.length && !todo.length) return false;
|
|
14156
|
-
if (dropped.length) say(
|
|
14508
|
+
if (dropped.length) say(import_chalk32.default.green(` \u2713 hidden from the MCP (utility endpoints, not tools): ${dropped.join(", ")}`));
|
|
14157
14509
|
if (todo.length) {
|
|
14158
|
-
say(
|
|
14510
|
+
say(import_chalk32.default.dim(` ${todo.length} tool${todo.length === 1 ? "" : "s"} need a description (what it does and returns):`));
|
|
14159
14511
|
const inquirer3 = interactive2 ? (await import("inquirer")).default : null;
|
|
14160
14512
|
for (const x of todo) {
|
|
14161
14513
|
const def = derivedDescription(x);
|
|
@@ -14165,7 +14517,7 @@ async function fixToolDescriptions(proj2, environment, interactive2, say) {
|
|
|
14165
14517
|
text = value?.trim() || def;
|
|
14166
14518
|
}
|
|
14167
14519
|
x.description = text;
|
|
14168
|
-
say(
|
|
14520
|
+
say(import_chalk32.default.green(` \u2713 ${x.name}: ${text}`));
|
|
14169
14521
|
}
|
|
14170
14522
|
}
|
|
14171
14523
|
cat.tools = kept;
|
|
@@ -14233,13 +14585,13 @@ function printSubmitSteps(r, mdPath, platforms = PLATFORMS, watching = false) {
|
|
|
14233
14585
|
const chatgpt = platforms.includes("chatgpt");
|
|
14234
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 ");
|
|
14235
14587
|
const steps = [
|
|
14236
|
-
`Test it yourself first: add ${
|
|
14237
|
-
...claude ? [`Claude: ${
|
|
14238
|
-
...chatgpt ? [`ChatGPT: ${
|
|
14588
|
+
`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
|
+
...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))}.`] : [],
|
|
14590
|
+
...chatgpt ? [`ChatGPT: ${import_chalk32.default.underline(PORTALS.chatgpt)} \u2192 upload ${import_chalk32.default.cyan(`connector-plugin-${r.report.project.id}.zip`)} \u2192 MCPs \u2192 Connect \u2192 Verify Domain \u2192 authorize \u2192 Scan Tools; paste the reviewer credentials under Review details; add the demo recording URL and the release notes; confirm the attestations. The MCP hostname is permanent for the OpenAI listing.`] : [],
|
|
14239
14591
|
watching ? open ? "A nightly re-check is on: it emails the team owner if the listing drifts or a new check is added." : "Keep the reviewer account alive. A nightly re-check is on: it emails the team owner if the listing drifts or a new check is added." : open ? "The nightly re-check is off (--no-watch); turn it on in the dashboard to be emailed when the listing drifts." : "Keep the reviewer account alive. The nightly re-check is off (--no-watch); turn it on in the dashboard to be emailed when the listing drifts."
|
|
14240
14592
|
];
|
|
14241
14593
|
console.log();
|
|
14242
|
-
console.log(
|
|
14594
|
+
console.log(import_chalk32.default.bold(" Submit (you do this part; the packet has every value to paste):"));
|
|
14243
14595
|
steps.forEach((s, i) => console.log(` ${i + 1}. ${s}`));
|
|
14244
14596
|
console.log();
|
|
14245
14597
|
}
|
|
@@ -14252,7 +14604,7 @@ init_connector_docs();
|
|
|
14252
14604
|
var import_fs12 = __toESM(require("fs"));
|
|
14253
14605
|
var import_os4 = __toESM(require("os"));
|
|
14254
14606
|
var import_path10 = __toESM(require("path"));
|
|
14255
|
-
var
|
|
14607
|
+
var import_chalk36 = __toESM(require("chalk"));
|
|
14256
14608
|
init_brand();
|
|
14257
14609
|
|
|
14258
14610
|
// src/lib/brand-surface.ts
|
|
@@ -14911,7 +15263,8 @@ function shipMcpMarkdown() {
|
|
|
14911
15263
|
8. **List it.** \`${run2} connector <name>\` (Claude; \`--compliant chatgpt\` or \`all\` for OpenAI's directory): the scorecard
|
|
14912
15264
|
first, then every open step in order \u2014 the MCP hostname (one CNAME the user adds at their DNS provider; relay the exact
|
|
14913
15265
|
record), the listing answers pre-filled from the spec, the reviewer account, the proof run \u2014 and the submission packet when
|
|
14914
|
-
every check passes.
|
|
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
|
|
14915
15268
|
can be finished.
|
|
14916
15269
|
The user submits the packet to Anthropic or OpenAI themselves.
|
|
14917
15270
|
|
|
@@ -15244,6 +15597,9 @@ and attestations. \`--compliant\` makes the ${Bb.thingShort} meet them to the le
|
|
|
15244
15597
|
- Which directory: \`--compliant\` alone is Claude. \`--compliant chatgpt\` is OpenAI's, \`--compliant all\` is both.
|
|
15245
15598
|
- From an OpenAPI URL: \`npx ${cli} create --compliant --target https://yourdomain.com/openapi.yaml --domain mcp.yourdomain.com\`.
|
|
15246
15599
|
On an existing ${Bb.thingShort}: \`npx ${cli} connector <name> --compliant --domain mcp.yourdomain.com\`.
|
|
15600
|
+
- A listing needs the hosted app: \`dev\` is for building and trying it, and a tunnel cannot be submitted. Have the production URL
|
|
15601
|
+
and any header the backend needs ready (\`--target-url\`, \`--upstream-header\`); it asks when they are missing.
|
|
15602
|
+
- A proof run made on a tunnel does not count: run \`npx ${cli} connector exercise <name>\` again after switching to the hosted app.
|
|
15247
15603
|
- It asks for the listing answers with every field pre-filled from the spec and cut to the portal's limits
|
|
15248
15604
|
(Enter accepts), and waits for ONE CNAME (mcp.yourdomain.com \u2192 proxy.abz.run) \u2014 the user adds it at their DNS
|
|
15249
15605
|
provider; relay the exact record it prints. For ChatGPT it also asks for the challenge token the OpenAI portal
|
|
@@ -15349,21 +15705,21 @@ async function runForPerson(found) {
|
|
|
15349
15705
|
const P = B.product;
|
|
15350
15706
|
const run2 = `npx ${B.cli}@latest`;
|
|
15351
15707
|
console.log();
|
|
15352
|
-
console.log(` ${
|
|
15708
|
+
console.log(` ${import_chalk36.default.bold(pitchText().split(". ")[0] + ".")}`);
|
|
15353
15709
|
console.log(` ${pitchText().split(". ").slice(1).join(". ")}`);
|
|
15354
15710
|
console.log();
|
|
15355
|
-
console.log(
|
|
15711
|
+
console.log(import_chalk36.default.dim(B.cli === "mcpblaze" ? ` With an AI assistant? Tell it: "Help me get my app listed in Claude and ChatGPT using ${run2} skills"` : ` With an AI assistant? Tell it: "Tell me what ${B.cli} can do for me using ${run2} skills"`));
|
|
15356
15712
|
console.log();
|
|
15357
15713
|
const report = folderReport(found, { forPerson: true });
|
|
15358
15714
|
for (const l of report.lines) console.log(` ${l.replace(/`/g, "")}`);
|
|
15359
15715
|
const port = backendPort(found);
|
|
15360
15716
|
if (B.cli === "mcpblaze" && report.ownApi && (found.specFile || found.specUrl)) {
|
|
15361
15717
|
const spec = found.specFile ?? found.specUrl;
|
|
15362
|
-
if (!await ask2(` ${
|
|
15363
|
-
console.log(` Any time: ${
|
|
15718
|
+
if (!await ask2(` ${import_chalk36.default.bold("?")} Make it a Claude connector now? (${run2} connector ${spec}) ${import_chalk36.default.dim("(Y/n)")} `)) {
|
|
15719
|
+
console.log(` Any time: ${import_chalk36.default.cyan(`${run2} connector ${spec}`)}${found.listening ? ` \xB7 or just the MCP server: ${import_chalk36.default.cyan(`${run2} dev --port ${found.listening}`)}` : ""}`);
|
|
15364
15720
|
return;
|
|
15365
15721
|
}
|
|
15366
|
-
console.log(
|
|
15722
|
+
console.log(import_chalk36.default.dim(` $ ${run2} connector ${spec}`));
|
|
15367
15723
|
const { spawnSync: spawnSync6 } = await import("child_process");
|
|
15368
15724
|
const r = spawnSync6(process.execPath, [process.argv[1], "connector", spec], { stdio: "inherit" });
|
|
15369
15725
|
process.exitCode = r.status ?? 0;
|
|
@@ -15371,11 +15727,11 @@ async function runForPerson(found) {
|
|
|
15371
15727
|
}
|
|
15372
15728
|
if (report.ownApi && found.listening) {
|
|
15373
15729
|
const args = ["dev", "--port", String(found.listening), ...found.specFile ? ["--openapi", found.specFile] : []];
|
|
15374
|
-
if (!await ask2(` ${
|
|
15375
|
-
console.log(` Any time: ${
|
|
15730
|
+
if (!await ask2(` ${import_chalk36.default.bold("?")} Put your API on port ${found.listening} behind ${P} now? ${import_chalk36.default.dim("(Y/n)")} `)) {
|
|
15731
|
+
console.log(` Any time: ${import_chalk36.default.cyan(`${run2} ${args.join(" ")}`)}`);
|
|
15376
15732
|
return;
|
|
15377
15733
|
}
|
|
15378
|
-
console.log(
|
|
15734
|
+
console.log(import_chalk36.default.dim(` $ ${run2} ${args.join(" ")}`));
|
|
15379
15735
|
const { spawnSync: spawnSync6 } = await import("child_process");
|
|
15380
15736
|
const r = spawnSync6(process.execPath, [process.argv[1], ...args], { stdio: "inherit" });
|
|
15381
15737
|
process.exitCode = r.status ?? 0;
|
|
@@ -15384,18 +15740,18 @@ async function runForPerson(found) {
|
|
|
15384
15740
|
if (report.ownApi) {
|
|
15385
15741
|
const args = ["dev", "--port", String(port ?? 3e3), ...found.specFile ? ["--openapi", found.specFile] : []];
|
|
15386
15742
|
const startHint = found.start ? `, once you start it (${found.start})` : ", once it runs";
|
|
15387
|
-
if (!await ask2(` ${
|
|
15388
|
-
console.log(` Any time: ${
|
|
15743
|
+
if (!await ask2(` ${import_chalk36.default.bold("?")} Put it behind ${P} now? It reaches your app on port ${port ?? 3e3}${startHint}. ${import_chalk36.default.dim("(Y/n)")} `)) {
|
|
15744
|
+
console.log(` Any time: ${import_chalk36.default.cyan(`${run2} ${args.join(" ")}`)}`);
|
|
15389
15745
|
return;
|
|
15390
15746
|
}
|
|
15391
|
-
console.log(
|
|
15747
|
+
console.log(import_chalk36.default.dim(` $ ${run2} ${args.join(" ")}`));
|
|
15392
15748
|
const { spawnSync: spawnSync6 } = await import("child_process");
|
|
15393
15749
|
const r = spawnSync6(process.execPath, [process.argv[1], ...args], { stdio: "inherit" });
|
|
15394
15750
|
process.exitCode = r.status ?? 0;
|
|
15395
15751
|
return;
|
|
15396
15752
|
}
|
|
15397
|
-
if (!await ask2(` ${
|
|
15398
|
-
console.log(` Your own app, when it runs: ${
|
|
15753
|
+
if (!await ask2(` ${import_chalk36.default.bold("?")} Create a demo app to try out ${P}? ${import_chalk36.default.dim("(Y/n)")} `)) {
|
|
15754
|
+
console.log(` Your own app, when it runs: ${import_chalk36.default.cyan(`${run2} dev --port <port>`)}`);
|
|
15399
15755
|
return;
|
|
15400
15756
|
}
|
|
15401
15757
|
const { runDemo: runDemo2 } = await Promise.resolve().then(() => (init_demo(), demo_exports));
|
|
@@ -15430,28 +15786,28 @@ function installSkill(opts) {
|
|
|
15430
15786
|
const rel = skillRelativePathFor();
|
|
15431
15787
|
const target2 = import_path10.default.resolve(process.cwd(), rel);
|
|
15432
15788
|
if (import_fs12.default.existsSync(target2) && !opts.force) {
|
|
15433
|
-
console.error(
|
|
15789
|
+
console.error(import_chalk36.default.yellow(`${rel} already exists \u2014 re-run with --force to overwrite it.`));
|
|
15434
15790
|
process.exit(1);
|
|
15435
15791
|
}
|
|
15436
15792
|
import_fs12.default.mkdirSync(import_path10.default.dirname(target2), { recursive: true });
|
|
15437
15793
|
import_fs12.default.writeFileSync(target2, md, "utf8");
|
|
15438
|
-
console.log(`${
|
|
15439
|
-
console.log(
|
|
15794
|
+
console.log(`${import_chalk36.default.green("\u2713")} Installed the ${import_chalk36.default.bold(skillNameFor())} skill: ${import_chalk36.default.bold(target2)}`);
|
|
15795
|
+
console.log(import_chalk36.default.dim(B.cli === "mcpblaze" ? ` Claude Code picks it up in this project. Say "make this an MCP server" or "list it in Claude" and it runs npx ${B.cli}@latest create / connector for you.` : ` Claude Code picks it up in this project. Say "ship this API" and it runs npx ${B.cli}@latest dev / create for you.`));
|
|
15440
15796
|
}
|
|
15441
15797
|
|
|
15442
15798
|
// src/commands/integration.ts
|
|
15443
|
-
var
|
|
15444
|
-
var
|
|
15799
|
+
var import_chalk37 = __toESM(require("chalk"));
|
|
15800
|
+
var import_ora14 = __toESM(require("ora"));
|
|
15445
15801
|
init_caller();
|
|
15446
15802
|
init_api();
|
|
15447
15803
|
init_brand();
|
|
15448
15804
|
function file(f2, indent2 = " ") {
|
|
15449
|
-
console.log(` ${
|
|
15805
|
+
console.log(` ${import_chalk37.default.cyan(f2.file)}`);
|
|
15450
15806
|
for (const line of f2.code.split("\n")) console.log(`${indent2}${line}`);
|
|
15451
15807
|
console.log();
|
|
15452
15808
|
}
|
|
15453
15809
|
function bullets(lines, indent2 = " ") {
|
|
15454
|
-
for (const l of lines) console.log(`${indent2}${
|
|
15810
|
+
for (const l of lines) console.log(`${indent2}${import_chalk37.default.dim("\u2022")} ${l}`);
|
|
15455
15811
|
}
|
|
15456
15812
|
async function runIntegration(name, opts = {}) {
|
|
15457
15813
|
const stackRaw = (opts.stack ?? "nextjs").trim().toLowerCase();
|
|
@@ -15462,7 +15818,7 @@ async function runIntegration(name, opts = {}) {
|
|
|
15462
15818
|
const project = name.trim().toLowerCase();
|
|
15463
15819
|
const caller = requireCaller();
|
|
15464
15820
|
if (opts.team && caller.mode === "anon") throw new Error("--team is not available in an anonymous workspace.");
|
|
15465
|
-
const spinner = opts.json ? null : (0,
|
|
15821
|
+
const spinner = opts.json ? null : (0, import_ora14.default)(`Rendering the ${stack} kit for ${project}\u2026`).start();
|
|
15466
15822
|
let kit;
|
|
15467
15823
|
try {
|
|
15468
15824
|
kit = await producer(caller, integrationKitCall(project, stack));
|
|
@@ -15476,44 +15832,44 @@ async function runIntegration(name, opts = {}) {
|
|
|
15476
15832
|
return;
|
|
15477
15833
|
}
|
|
15478
15834
|
console.log();
|
|
15479
|
-
console.log(
|
|
15480
|
-
console.log(
|
|
15835
|
+
console.log(import_chalk37.default.bold(`Integration kit \u2014 ${kit.project} (${kit.stack})`));
|
|
15836
|
+
console.log(import_chalk37.default.dim(` Edit only the frontend and the frontend-server. Your backend changes in one way: it trusts the ${B.product} identity headers.`));
|
|
15481
15837
|
console.log();
|
|
15482
|
-
console.log(` ${
|
|
15483
|
-
console.log(` ${
|
|
15484
|
-
console.log(` ${
|
|
15485
|
-
console.log(` ${
|
|
15838
|
+
console.log(` ${import_chalk37.default.dim("Proxy: ")} ${import_chalk37.default.bold(kit.urls.proxy)}`);
|
|
15839
|
+
console.log(` ${import_chalk37.default.dim("MCP: ")} ${kit.urls.mcp.join(import_chalk37.default.dim(" \xB7 "))}`);
|
|
15840
|
+
console.log(` ${import_chalk37.default.dim("Portal: ")} ${kit.urls.portal}`);
|
|
15841
|
+
console.log(` ${import_chalk37.default.dim("Spec: ")} ${kit.urls.openapi}`);
|
|
15486
15842
|
const w = kit["1_frontend_widgets"];
|
|
15487
15843
|
console.log();
|
|
15488
|
-
console.log(
|
|
15489
|
-
console.log(
|
|
15844
|
+
console.log(import_chalk37.default.bold("1. Frontend \u2014 the widgets (keys + chat)"));
|
|
15845
|
+
console.log(import_chalk37.default.dim(` ${w.note}`));
|
|
15490
15846
|
console.log();
|
|
15491
15847
|
file(w.frontend);
|
|
15492
15848
|
for (const r of w.server_routes) file(r);
|
|
15493
15849
|
const c = kit["2_frontend_server_call_through"];
|
|
15494
|
-
console.log(
|
|
15850
|
+
console.log(import_chalk37.default.bold("2. Frontend-server \u2014 call the proxy with the server key and say who is calling"));
|
|
15495
15851
|
console.log();
|
|
15496
15852
|
file(c);
|
|
15497
15853
|
const t = kit["3_backend_trust_contract"];
|
|
15498
|
-
console.log(
|
|
15499
|
-
for (const [h, why] of Object.entries(t.headers_you_receive)) console.log(` ${
|
|
15854
|
+
console.log(import_chalk37.default.bold("3. Backend \u2014 trust the identity headers, and nothing else"));
|
|
15855
|
+
for (const [h, why] of Object.entries(t.headers_you_receive)) console.log(` ${import_chalk37.default.cyan(h)} \u2014 ${why}`);
|
|
15500
15856
|
console.log();
|
|
15501
|
-
console.log(` ${
|
|
15857
|
+
console.log(` ${import_chalk37.default.dim("How to verify:")}`);
|
|
15502
15858
|
bullets(t.how_to_verify);
|
|
15503
15859
|
console.log();
|
|
15504
|
-
console.log(` ${
|
|
15860
|
+
console.log(` ${import_chalk37.default.dim("Never:")}`);
|
|
15505
15861
|
bullets(t.what_not_to_do);
|
|
15506
15862
|
console.log();
|
|
15507
|
-
console.log(
|
|
15508
|
-
for (const [k, v] of Object.entries(kit["4_env_vars"])) console.log(` ${
|
|
15863
|
+
console.log(import_chalk37.default.bold("4. Env vars (server only)"));
|
|
15864
|
+
for (const [k, v] of Object.entries(kit["4_env_vars"])) console.log(` ${import_chalk37.default.cyan(k)} ${import_chalk37.default.dim("=")} ${v}`);
|
|
15509
15865
|
const o = kit["5_what_stays_open"];
|
|
15510
15866
|
console.log();
|
|
15511
|
-
console.log(
|
|
15512
|
-
console.log(` ${
|
|
15513
|
-
console.log(` ${
|
|
15514
|
-
console.log(
|
|
15867
|
+
console.log(import_chalk37.default.bold("5. What stays open"));
|
|
15868
|
+
console.log(` ${import_chalk37.default.dim("Protected by a rule:")} ${o.protected.length ? o.protected.join(", ") : import_chalk37.default.dim("nothing")}`);
|
|
15869
|
+
console.log(` ${import_chalk37.default.dim("Open to any identified caller:")} ${o.open.length ? o.open.join(", ") : import_chalk37.default.dim("nothing")}`);
|
|
15870
|
+
console.log(import_chalk37.default.dim(` ${o.note}`));
|
|
15515
15871
|
console.log();
|
|
15516
|
-
console.log(
|
|
15872
|
+
console.log(import_chalk37.default.dim(` Raw JSON: npx ${B.cli} integration ${kit.project} --stack ${kit.stack} --json`));
|
|
15517
15873
|
console.log();
|
|
15518
15874
|
}
|
|
15519
15875
|
|
|
@@ -15521,14 +15877,14 @@ async function runIntegration(name, opts = {}) {
|
|
|
15521
15877
|
init_claim();
|
|
15522
15878
|
|
|
15523
15879
|
// src/commands/logout.ts
|
|
15524
|
-
var
|
|
15880
|
+
var import_chalk38 = __toESM(require("chalk"));
|
|
15525
15881
|
init_auth();
|
|
15526
15882
|
init_consumer_auth();
|
|
15527
15883
|
async function runLogout(opts = {}) {
|
|
15528
15884
|
const creds = loadCredentials();
|
|
15529
15885
|
const consumer = loadConsumer();
|
|
15530
15886
|
if (!creds && !consumer) {
|
|
15531
|
-
console.log(
|
|
15887
|
+
console.log(import_chalk38.default.yellow("You were not logged in."));
|
|
15532
15888
|
return;
|
|
15533
15889
|
}
|
|
15534
15890
|
let target2;
|
|
@@ -15555,16 +15911,16 @@ async function runLogout(opts = {}) {
|
|
|
15555
15911
|
}
|
|
15556
15912
|
if (target2 === "producer" || target2 === "all") {
|
|
15557
15913
|
clearCredentials();
|
|
15558
|
-
console.log(
|
|
15914
|
+
console.log(import_chalk38.default.green(`\u2714 Logged out of API Producer${creds?.githubHandle ? ` (${creds.githubHandle})` : ""}.`));
|
|
15559
15915
|
}
|
|
15560
15916
|
if (target2 === "consumer" || target2 === "all") {
|
|
15561
15917
|
clearConsumer();
|
|
15562
|
-
console.log(
|
|
15918
|
+
console.log(import_chalk38.default.green(`\u2714 Logged out of API Consumer${consumer?.tenant ? ` (${consumer.tenant})` : ""}.`));
|
|
15563
15919
|
}
|
|
15564
15920
|
}
|
|
15565
15921
|
|
|
15566
15922
|
// src/commands/flush.ts
|
|
15567
|
-
var
|
|
15923
|
+
var import_chalk39 = __toESM(require("chalk"));
|
|
15568
15924
|
var fs21 = __toESM(require("fs"));
|
|
15569
15925
|
var path17 = __toESM(require("path"));
|
|
15570
15926
|
init_auth();
|
|
@@ -15572,18 +15928,18 @@ init_external_mcp();
|
|
|
15572
15928
|
async function runFlush(opts) {
|
|
15573
15929
|
const { stopDemos: stopDemos2 } = await Promise.resolve().then(() => (init_demo(), demo_exports));
|
|
15574
15930
|
const demos = await stopDemos2().catch(() => 0);
|
|
15575
|
-
if (demos) console.log(
|
|
15931
|
+
if (demos) console.log(import_chalk39.default.dim(` Stopped ${demos} running demo(s).`));
|
|
15576
15932
|
const dir = getApiblazeDir();
|
|
15577
15933
|
const entries2 = fs21.existsSync(dir) ? fs21.readdirSync(dir) : [];
|
|
15578
15934
|
if (!entries2.length) {
|
|
15579
|
-
console.log(
|
|
15935
|
+
console.log(import_chalk39.default.dim("Nothing to flush \u2014 no local apiblaze config found."));
|
|
15580
15936
|
return;
|
|
15581
15937
|
}
|
|
15582
|
-
console.log(
|
|
15583
|
-
console.log(
|
|
15584
|
-
console.log(
|
|
15585
|
-
console.log(
|
|
15586
|
-
console.log(
|
|
15938
|
+
console.log(import_chalk39.default.yellow("This removes ALL local apiblaze state from this machine:"));
|
|
15939
|
+
console.log(import_chalk39.default.dim(" \u2022 login credentials & anonymous-workspace keys"));
|
|
15940
|
+
console.log(import_chalk39.default.dim(" \u2022 saved apichats \u2014 their DP API keys and transcripts"));
|
|
15941
|
+
console.log(import_chalk39.default.dim(" \u2022 your BYO LLM key and any cached secrets"));
|
|
15942
|
+
console.log(import_chalk39.default.dim(` (${entries2.length} item(s) in ${dir})`));
|
|
15587
15943
|
const footprint = apiblazeMcpFootprint();
|
|
15588
15944
|
const mdFiles = ["claude", "codex"].map((k) => ({ k, file: agentInstructionFile(k) })).filter(({ file: file2 }) => {
|
|
15589
15945
|
try {
|
|
@@ -15593,19 +15949,19 @@ async function runFlush(opts) {
|
|
|
15593
15949
|
}
|
|
15594
15950
|
});
|
|
15595
15951
|
if (footprint.claude.length || footprint.codex.length) {
|
|
15596
|
-
console.log(
|
|
15952
|
+
console.log(import_chalk39.default.dim(` \u2022 MCP servers installed in other tools: ${[...footprint.claude, ...footprint.codex].join(", ")}`));
|
|
15597
15953
|
}
|
|
15598
15954
|
if (mdFiles.length) {
|
|
15599
|
-
console.log(
|
|
15955
|
+
console.log(import_chalk39.default.dim(` \u2022 the "use this MCP" blocks in ${mdFiles.map((m) => path17.basename(m.file)).join(" and ")} (here)`));
|
|
15600
15956
|
}
|
|
15601
|
-
console.log(
|
|
15957
|
+
console.log(import_chalk39.default.dim(" It does NOT delete server-side proxies/keys \u2014 those GC or are revoked separately."));
|
|
15602
15958
|
if (!opts.yes) {
|
|
15603
15959
|
const { default: inquirer3 } = await import("inquirer");
|
|
15604
15960
|
const { ok } = await inquirer3.prompt([
|
|
15605
15961
|
{ type: "confirm", name: "ok", message: "Delete all local state?", default: false }
|
|
15606
15962
|
]);
|
|
15607
15963
|
if (!ok) {
|
|
15608
|
-
console.log(
|
|
15964
|
+
console.log(import_chalk39.default.dim("Cancelled \u2014 nothing removed."));
|
|
15609
15965
|
return;
|
|
15610
15966
|
}
|
|
15611
15967
|
}
|
|
@@ -15615,14 +15971,14 @@ async function runFlush(opts) {
|
|
|
15615
15971
|
fs21.rmSync(path17.join(dir, e), { recursive: true, force: true });
|
|
15616
15972
|
removed++;
|
|
15617
15973
|
} catch (err) {
|
|
15618
|
-
console.log(
|
|
15974
|
+
console.log(import_chalk39.default.red(` \u2717 could not remove ${e}: ${err instanceof Error ? err.message : "error"}`));
|
|
15619
15975
|
}
|
|
15620
15976
|
}
|
|
15621
15977
|
for (const name of footprint.claude) {
|
|
15622
|
-
console.log(removeClaudeServer(name) ? ` ${
|
|
15978
|
+
console.log(removeClaudeServer(name) ? ` ${import_chalk39.default.dim("\u2022")} removed MCP ${import_chalk39.default.bold(name)} from Claude CLI` : import_chalk39.default.yellow(` \u2717 could not remove MCP ${name} from Claude CLI`));
|
|
15623
15979
|
}
|
|
15624
15980
|
for (const name of footprint.codex) {
|
|
15625
|
-
console.log(removeCodexServer(name) ? ` ${
|
|
15981
|
+
console.log(removeCodexServer(name) ? ` ${import_chalk39.default.dim("\u2022")} removed MCP ${import_chalk39.default.bold(name)} from Codex` : import_chalk39.default.yellow(` \u2717 could not remove MCP ${name} from Codex`));
|
|
15626
15982
|
}
|
|
15627
15983
|
for (const { k, file: file2 } of mdFiles) {
|
|
15628
15984
|
let text = "";
|
|
@@ -15634,17 +15990,17 @@ async function runFlush(opts) {
|
|
|
15634
15990
|
const names = [...text.matchAll(/<!-- apiblaze:mcp:([^\s>]+) -->/g)].map((m) => m[1]);
|
|
15635
15991
|
let cleared = 0;
|
|
15636
15992
|
for (const n of new Set(names)) if (removeAgentInstruction(k, n)) cleared++;
|
|
15637
|
-
if (cleared) console.log(` ${
|
|
15993
|
+
if (cleared) console.log(` ${import_chalk39.default.dim("\u2022")} removed ${cleared} APIblaze block(s) from ${import_chalk39.default.bold(path17.basename(file2))}`);
|
|
15638
15994
|
}
|
|
15639
|
-
console.log(`${
|
|
15640
|
-
console.log(
|
|
15995
|
+
console.log(`${import_chalk39.default.green("\u2714")} Flushed \u2014 removed ${removed} item(s). You're fully logged out and clean.`);
|
|
15996
|
+
console.log(import_chalk39.default.dim(" Next `apiblaze create` starts a brand-new workspace with nothing carried over."));
|
|
15641
15997
|
}
|
|
15642
15998
|
|
|
15643
15999
|
// src/cli.ts
|
|
15644
16000
|
init_demo();
|
|
15645
16001
|
|
|
15646
16002
|
// src/commands/whoami.ts
|
|
15647
|
-
var
|
|
16003
|
+
var import_chalk40 = __toESM(require("chalk"));
|
|
15648
16004
|
init_auth();
|
|
15649
16005
|
init_consumer_auth();
|
|
15650
16006
|
init_anon_cred();
|
|
@@ -15668,54 +16024,54 @@ async function runWhoami(opts = {}) {
|
|
|
15668
16024
|
}, null, 2));
|
|
15669
16025
|
return;
|
|
15670
16026
|
}
|
|
15671
|
-
console.log(
|
|
16027
|
+
console.log(import_chalk40.default.bold("API Producer"));
|
|
15672
16028
|
if (!creds) {
|
|
15673
|
-
console.log(
|
|
16029
|
+
console.log(import_chalk40.default.dim(" Not logged in. Run `apiblaze login`."));
|
|
15674
16030
|
} else {
|
|
15675
16031
|
const who = creds.githubHandle ?? creds.email ?? creds.apiblazeUserId ?? "unknown";
|
|
15676
|
-
console.log(` ${
|
|
16032
|
+
console.log(` ${import_chalk40.default.cyan("Signed in as")} ${import_chalk40.default.bold(who)}`);
|
|
15677
16033
|
if (creds.email && creds.email !== who) console.log(` Email: ${creds.email}`);
|
|
15678
|
-
if (creds.teamName || creds.teamId) console.log(` Team: ${
|
|
15679
|
-
if (creds.activeTenant) console.log(` Tenant scope: ${
|
|
15680
|
-
if (Date.now() >= creds.expiresAt) console.log(
|
|
16034
|
+
if (creds.teamName || creds.teamId) console.log(` Team: ${import_chalk40.default.bold(creds.teamName ?? creds.teamId)}`);
|
|
16035
|
+
if (creds.activeTenant) console.log(` Tenant scope: ${import_chalk40.default.bold(creds.activeTenant)} ${import_chalk40.default.dim("(clear: `apiblaze tenant use --clear`)")}`);
|
|
16036
|
+
if (Date.now() >= creds.expiresAt) console.log(import_chalk40.default.yellow(" \u26A0 Session expired \u2014 run `apiblaze login`."));
|
|
15681
16037
|
}
|
|
15682
16038
|
if (anon) {
|
|
15683
16039
|
const masked = `${anon.cp_key.slice(0, 11)}\u2026${anon.cp_key.slice(-4)}`;
|
|
15684
|
-
console.log(
|
|
15685
|
-
console.log(` Team: ${
|
|
15686
|
-
console.log(` CP key: ${
|
|
16040
|
+
console.log(import_chalk40.default.bold("\nAnonymous workspace") + import_chalk40.default.dim(" (created without an account \u2014 unclaimed)"));
|
|
16041
|
+
console.log(` Team: ${import_chalk40.default.bold(anon.team_id)}`);
|
|
16042
|
+
console.log(` CP key: ${import_chalk40.default.bold(masked)} ${import_chalk40.default.dim("controls the proxies you created anonymously")}`);
|
|
15687
16043
|
console.log(
|
|
15688
|
-
|
|
16044
|
+
import_chalk40.default.dim(` Keep it: ${import_chalk40.default.cyan("apiblaze login")} then ${import_chalk40.default.cyan("apiblaze claim")}`) + (anon.claim_code ? import_chalk40.default.dim(` \xB7 from another machine: ${import_chalk40.default.cyan(`apiblaze claim ${anon.claim_code}`)}`) : "")
|
|
15689
16045
|
);
|
|
15690
|
-
if (creds) console.log(
|
|
16046
|
+
if (creds) console.log(import_chalk40.default.dim(" (you're logged in \u2014 `apiblaze claim` folds this workspace into your account)"));
|
|
15691
16047
|
}
|
|
15692
|
-
console.log(
|
|
16048
|
+
console.log(import_chalk40.default.bold("\nAPI Consumer"));
|
|
15693
16049
|
if (!consumer) {
|
|
15694
|
-
console.log(
|
|
16050
|
+
console.log(import_chalk40.default.dim(" Not logged in. Run `apiblaze consumer login`."));
|
|
15695
16051
|
} else {
|
|
15696
|
-
console.log(` ${
|
|
15697
|
-
console.log(` Tenant: ${
|
|
15698
|
-
if (Date.now() >= consumer.expiresAt) console.log(
|
|
16052
|
+
console.log(` ${import_chalk40.default.cyan("Signed in as")} ${import_chalk40.default.bold(consumer.email ?? consumer.tenant)}`);
|
|
16053
|
+
console.log(` Tenant: ${import_chalk40.default.bold(consumer.tenant)}`);
|
|
16054
|
+
if (Date.now() >= consumer.expiresAt) console.log(import_chalk40.default.yellow(" \u26A0 Token expired \u2014 it will refresh on next use, or re-run `apiblaze consumer login`."));
|
|
15699
16055
|
}
|
|
15700
16056
|
}
|
|
15701
16057
|
|
|
15702
16058
|
// src/commands/team.ts
|
|
15703
|
-
var
|
|
15704
|
-
var
|
|
16059
|
+
var import_chalk41 = __toESM(require("chalk"));
|
|
16060
|
+
var import_ora15 = __toESM(require("ora"));
|
|
15705
16061
|
init_auth();
|
|
15706
16062
|
init_api();
|
|
15707
16063
|
init_admin();
|
|
15708
16064
|
function fail5(message, hint) {
|
|
15709
|
-
console.error(
|
|
15710
|
-
if (hint) console.error(
|
|
16065
|
+
console.error(import_chalk41.default.red(`Error: ${message}`));
|
|
16066
|
+
if (hint) console.error(import_chalk41.default.dim(hint));
|
|
15711
16067
|
process.exit(1);
|
|
15712
16068
|
}
|
|
15713
16069
|
function activate(creds, team) {
|
|
15714
16070
|
const next = { ...creds, teamId: team.teamId, teamName: team.name };
|
|
15715
16071
|
if (creds.teamId !== team.teamId) delete next.activeTenant;
|
|
15716
16072
|
saveCredentials(next);
|
|
15717
|
-
console.log(
|
|
15718
|
-
\u2714 Active team: ${
|
|
16073
|
+
console.log(import_chalk41.default.green(`
|
|
16074
|
+
\u2714 Active team: ${import_chalk41.default.bold(team.name)}`));
|
|
15719
16075
|
}
|
|
15720
16076
|
async function promptName(message) {
|
|
15721
16077
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -15723,14 +16079,14 @@ async function promptName(message) {
|
|
|
15723
16079
|
const { name } = await inquirer3.prompt([{ type: "input", name: "name", message }]);
|
|
15724
16080
|
const trimmed = (name ?? "").trim();
|
|
15725
16081
|
if (trimmed.length >= 2) return trimmed;
|
|
15726
|
-
console.log(
|
|
16082
|
+
console.log(import_chalk41.default.yellow(" Team name must be at least 2 characters.\n"));
|
|
15727
16083
|
}
|
|
15728
16084
|
}
|
|
15729
16085
|
async function createAndActivate(creds, name) {
|
|
15730
16086
|
if (!creds.apiblazeUserId) {
|
|
15731
16087
|
fail5("Your login is missing its user id.", "Run `apiblaze login` again, then retry.");
|
|
15732
16088
|
}
|
|
15733
|
-
const spinner = (0,
|
|
16089
|
+
const spinner = (0, import_ora15.default)(`Creating team "${name}"...`).start();
|
|
15734
16090
|
let created;
|
|
15735
16091
|
try {
|
|
15736
16092
|
created = await admin({
|
|
@@ -15745,7 +16101,7 @@ async function createAndActivate(creds, name) {
|
|
|
15745
16101
|
}
|
|
15746
16102
|
spinner.stop();
|
|
15747
16103
|
if (!created?.team_id) fail5("The server created no team id.");
|
|
15748
|
-
console.log(`${
|
|
16104
|
+
console.log(`${import_chalk41.default.green("\u2714")} Team created: ${import_chalk41.default.bold(created.name ?? name)} ${import_chalk41.default.dim(`(${created.team_id})`)}`);
|
|
15749
16105
|
activate(creds, { teamId: created.team_id, name: created.name ?? name });
|
|
15750
16106
|
}
|
|
15751
16107
|
async function runTeam(arg, opts = {}) {
|
|
@@ -15802,11 +16158,11 @@ async function runTeam(arg, opts = {}) {
|
|
|
15802
16158
|
default: creds.teamId,
|
|
15803
16159
|
choices: [
|
|
15804
16160
|
...teams.map((t) => ({
|
|
15805
|
-
name: t.teamId === creds.teamId ? `${t.name} ${
|
|
16161
|
+
name: t.teamId === creds.teamId ? `${t.name} ${import_chalk41.default.dim("(current)")}` : t.name,
|
|
15806
16162
|
value: t.teamId
|
|
15807
16163
|
})),
|
|
15808
16164
|
new inquirer3.Separator(),
|
|
15809
|
-
{ name:
|
|
16165
|
+
{ name: import_chalk41.default.green("\uFF0B Create a new team"), value: CREATE }
|
|
15810
16166
|
]
|
|
15811
16167
|
}]);
|
|
15812
16168
|
if (picked === CREATE) {
|
|
@@ -15821,7 +16177,7 @@ async function runTeam(arg, opts = {}) {
|
|
|
15821
16177
|
init_allow_oauth_registration();
|
|
15822
16178
|
|
|
15823
16179
|
// src/commands/docs.ts
|
|
15824
|
-
var
|
|
16180
|
+
var import_chalk42 = __toESM(require("chalk"));
|
|
15825
16181
|
init_apichat();
|
|
15826
16182
|
init_brand();
|
|
15827
16183
|
var MCPBLAZE_DOCS_NOTE = 'The user is on MCPblaze: say "MCP server", never "proxy". The front door is create_mcp_server (an MCP server from an OpenAPI spec; it returns the connector URL and the scorecard page), get_readiness (where a listing stands and what is next) and, in the terminal, `npx mcpblaze connector <name>`. The details \u2014 the custom hostname, the listing fields, attestations, the reviewer account, the packet \u2014 are finished on the scorecard page, not here. Passing the checks means the MCP meets the requirements we can verify; the user submits, the directories decide.';
|
|
@@ -15834,32 +16190,32 @@ async function runDocsWeb() {
|
|
|
15834
16190
|
const { openRegistrationAuthorization: openRegistrationAuthorization2 } = await Promise.resolve().then(() => (init_allow_oauth_registration(), allow_oauth_registration_exports));
|
|
15835
16191
|
const creds = loadCredentials2();
|
|
15836
16192
|
if (!creds?.teamId) {
|
|
15837
|
-
console.error(
|
|
15838
|
-
console.error(
|
|
16193
|
+
console.error(import_chalk42.default.red("Error: Connecting a web chatbot needs an account."));
|
|
16194
|
+
console.error(import_chalk42.default.dim("Run `apiblaze login` first (free)."));
|
|
15839
16195
|
process.exit(1);
|
|
15840
16196
|
}
|
|
15841
16197
|
const tenant = opTenantForTeam3(creds.teamId);
|
|
15842
16198
|
const url = B.cli === "mcpblaze" ? `https://externalauth-${tenant}.mcpblaze.com/1.0.0/prod` : `https://externalauth-${tenant}.mcp.apiblaze.com/1.0.0/prod`;
|
|
15843
|
-
console.log(
|
|
16199
|
+
console.log(import_chalk42.default.bold(`
|
|
15844
16200
|
Your personal ${B.product} MCP URL \u2014 paste it into claude.ai or ChatGPT:
|
|
15845
16201
|
`));
|
|
15846
|
-
console.log(" " +
|
|
15847
|
-
console.log(
|
|
16202
|
+
console.log(" " + import_chalk42.default.bgCyan.black.bold(` ${url} `));
|
|
16203
|
+
console.log(import_chalk42.default.dim(`
|
|
15848
16204
|
It is scoped to your team (${creds.teamName ?? creds.teamId}) \u2014 nobody else's account is reachable through it.`));
|
|
15849
16205
|
const ok = await openRegistrationAuthorization2(creds.teamId, tenant, 900);
|
|
15850
16206
|
console.log(ok ? `
|
|
15851
|
-
${
|
|
16207
|
+
${import_chalk42.default.green("\u2714")} Connection pre-authorized (single use, 15 min) \u2014 connect now and it won't ask you to approve the app.` : import_chalk42.default.yellow(`
|
|
15852
16208
|
Could not pre-authorize the connection. If the sign-in says the app needs verifying, run:
|
|
15853
16209
|
${B.cli} allowoauthregistration ${B.cli === "mcpblaze" ? "--control-plane" : "--apiblaze"}`));
|
|
15854
|
-
console.log(
|
|
16210
|
+
console.log(import_chalk42.default.dim("\n Re-run this command any time to authorize another client."));
|
|
15855
16211
|
}
|
|
15856
16212
|
async function runDocs(opts) {
|
|
15857
16213
|
console.log("");
|
|
15858
|
-
console.log(
|
|
15859
|
-
console.log(
|
|
15860
|
-
console.log(
|
|
15861
|
-
console.log(
|
|
15862
|
-
console.log(
|
|
16214
|
+
console.log(import_chalk42.default.bgYellow.black.bold(" This is not a docs search \u2014 the chatbot can CHANGE your account. "));
|
|
16215
|
+
console.log(import_chalk42.default.yellow(` It connects ${import_chalk42.default.bold("Claude Code")} or ${import_chalk42.default.bold("Codex")} to the real ${B.product} ${B.cli === "mcpblaze" ? "MCP server" : "API"}, acting as you:`));
|
|
16216
|
+
console.log(import_chalk42.default.yellow(B.cli === "mcpblaze" ? " it can create MCP servers, change their settings and delete them for real." : " it can create proxies, add logins, invite teammates and point domains for real."));
|
|
16217
|
+
console.log(import_chalk42.default.yellow(" Destructive actions (deleting proxies, teams or products) are deliberately not exposed."));
|
|
16218
|
+
console.log(import_chalk42.default.dim(' Read-only questions ("what proxies do I have?") are of course fine.'));
|
|
15863
16219
|
console.log("");
|
|
15864
16220
|
if (!opts.yes && !opts.prompt && process.stdin.isTTY) {
|
|
15865
16221
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -15870,7 +16226,7 @@ async function runDocs(opts) {
|
|
|
15870
16226
|
message: "Continue, knowing the chatbot can act on your account?"
|
|
15871
16227
|
}]);
|
|
15872
16228
|
if (!ok) {
|
|
15873
|
-
console.log(
|
|
16229
|
+
console.log(import_chalk42.default.dim(`
|
|
15874
16230
|
Nothing connected. The written docs are at ${B.cli === "mcpblaze" ? "https://mcpblaze.com" : "https://www.apiblaze.com/docs"}`));
|
|
15875
16231
|
return;
|
|
15876
16232
|
}
|
|
@@ -15893,13 +16249,13 @@ async function runDocs(opts) {
|
|
|
15893
16249
|
}
|
|
15894
16250
|
|
|
15895
16251
|
// src/commands/authz.ts
|
|
15896
|
-
var
|
|
16252
|
+
var import_chalk44 = __toESM(require("chalk"));
|
|
15897
16253
|
init_auth();
|
|
15898
16254
|
init_api();
|
|
15899
16255
|
init_agent_chat();
|
|
15900
16256
|
|
|
15901
16257
|
// src/lib/authz-apply.ts
|
|
15902
|
-
var
|
|
16258
|
+
var import_chalk43 = __toESM(require("chalk"));
|
|
15903
16259
|
init_api();
|
|
15904
16260
|
async function gatherSampleIds(projectId, apiVersion, cap2 = 50) {
|
|
15905
16261
|
try {
|
|
@@ -15924,10 +16280,10 @@ async function gatherSampleIds(projectId, apiVersion, cap2 = 50) {
|
|
|
15924
16280
|
}
|
|
15925
16281
|
function maybeWarnNoTraffic(total, projectArg) {
|
|
15926
16282
|
if (total >= 3) return;
|
|
15927
|
-
console.log(
|
|
15928
|
-
console.log(
|
|
15929
|
-
console.log(
|
|
15930
|
-
console.log(
|
|
16283
|
+
console.log(import_chalk43.default.yellow(total === 0 ? " \u26A0 No captured traffic for this API yet." : ` \u26A0 Only ${total} captured request(s) so far.`));
|
|
16284
|
+
console.log(import_chalk43.default.dim(" The authorization agent designs much better models & rules from REAL requests"));
|
|
16285
|
+
console.log(import_chalk43.default.dim(" (it sees who is allowed vs denied). Send some through your proxy first \u2014"));
|
|
16286
|
+
console.log(import_chalk43.default.dim(` curl a few endpoints, or `) + import_chalk43.default.cyan("npx apiblaze dev") + import_chalk43.default.dim(` \u2014 then re-run.
|
|
15931
16287
|
`));
|
|
15932
16288
|
}
|
|
15933
16289
|
async function applyProposal(opts) {
|
|
@@ -15936,12 +16292,12 @@ async function applyProposal(opts) {
|
|
|
15936
16292
|
{
|
|
15937
16293
|
const m = await agentCall(`/projects/${projectId}/${apiVersion}/policies/model?tenantId=${encodeURIComponent(tenant)}`, "POST", proposal.model);
|
|
15938
16294
|
if (m.status === 404) {
|
|
15939
|
-
log4(
|
|
16295
|
+
log4(import_chalk43.default.red(` Project ${projectId} ${apiVersion} not found on the authorization service \u2014 check \`apiblaze projects\`.
|
|
15940
16296
|
`));
|
|
15941
16297
|
return { ok: 0, fail: 0, enabled: false, aborted: true };
|
|
15942
16298
|
}
|
|
15943
16299
|
if (m.status >= 400) {
|
|
15944
|
-
log4(
|
|
16300
|
+
log4(import_chalk43.default.red(` Model save failed (${m.status}): ${m.data?.error ?? ""}
|
|
15945
16301
|
`));
|
|
15946
16302
|
return { ok: 0, fail: 0, enabled: false, aborted: true };
|
|
15947
16303
|
}
|
|
@@ -15973,17 +16329,17 @@ async function applyProposal(opts) {
|
|
|
15973
16329
|
}
|
|
15974
16330
|
}
|
|
15975
16331
|
if (fail7 > 0) {
|
|
15976
|
-
log4(
|
|
15977
|
-
for (const f2 of failed) log4(
|
|
16332
|
+
log4(import_chalk43.default.red(` ${fail7} of ${proposal.routes.length} route rule(s) could not be saved:`));
|
|
16333
|
+
for (const f2 of failed) log4(import_chalk43.default.red(` ${f2}`));
|
|
15978
16334
|
if (enable) {
|
|
15979
|
-
log4(
|
|
16335
|
+
log4(import_chalk43.default.red(" Enforcement NOT turned on \u2014 a route without a saved rule is left open by the gateway. Fix the rule(s) above and re-run with --enforce.\n"));
|
|
15980
16336
|
}
|
|
15981
16337
|
return { ok, fail: fail7, enabled: false, aborted: true, failed };
|
|
15982
16338
|
}
|
|
15983
16339
|
if (enable) {
|
|
15984
16340
|
const e = await agentCall(`/projects/${projectId}/${apiVersion}`, "PATCH", { authorization: { enforce_authorization: true }, tenant });
|
|
15985
16341
|
if (e.status >= 400) {
|
|
15986
|
-
log4(
|
|
16342
|
+
log4(import_chalk43.default.red(` Enforced ${ok} route(s) but turning on the project-level switch failed (${e.status}). Toggle "Enforce Authorization" on in the dashboard.
|
|
15987
16343
|
`));
|
|
15988
16344
|
return { ok, fail: fail7, enabled: false, failed };
|
|
15989
16345
|
}
|
|
@@ -16011,19 +16367,19 @@ async function runAuthz(projectArg, apiVersionArg) {
|
|
|
16011
16367
|
const proposal = ctx.proposal;
|
|
16012
16368
|
const r = await applyProposal({ projectId, apiVersion, tenant, proposal, enable, log: ctx.log });
|
|
16013
16369
|
if (r.aborted) {
|
|
16014
|
-
ctx.log(
|
|
16370
|
+
ctx.log(import_chalk44.default.red(enable ? ` Enforcement NOT on \u2014 ${r.fail || "the"} route rule(s) failed. Fix them and /enable again.
|
|
16015
16371
|
` : ` Not published \u2014 ${r.fail || "the"} route rule(s) failed. Fix them and /publish again.
|
|
16016
16372
|
`));
|
|
16017
16373
|
return;
|
|
16018
16374
|
}
|
|
16019
16375
|
if (enable && r.enabled) {
|
|
16020
|
-
ctx.log(
|
|
16021
|
-
ctx.log(
|
|
16022
|
-
ctx.log(
|
|
16376
|
+
ctx.log(import_chalk44.default.green(` \u2713 Enforcement ON \u2014 ${r.ok} route(s) + project switch on.`));
|
|
16377
|
+
ctx.log(import_chalk44.default.dim(" Route config takes up to ~90 s to reach every edge. Undo any time: ") + import_chalk44.default.cyan(`apiblaze config ${projectArg} authorization.enforce_authorization false`));
|
|
16378
|
+
ctx.log(import_chalk44.default.dim(" Done here? Type /exit to leave the chat (your rule stays on).\n"));
|
|
16023
16379
|
} else if (enable) {
|
|
16024
|
-
ctx.log(
|
|
16380
|
+
ctx.log(import_chalk44.default.red(" Enforcement NOT on \u2014 the routes are set but the project switch did not flip (see above).\n"));
|
|
16025
16381
|
} else {
|
|
16026
|
-
ctx.log(
|
|
16382
|
+
ctx.log(import_chalk44.default.green(` \u2713 Published (shadow): model + ${r.ok} route(s). Review, then /enable.
|
|
16027
16383
|
`));
|
|
16028
16384
|
}
|
|
16029
16385
|
}
|
|
@@ -16050,8 +16406,8 @@ async function runAuthz(projectArg, apiVersionArg) {
|
|
|
16050
16406
|
}
|
|
16051
16407
|
|
|
16052
16408
|
// src/commands/rule.ts
|
|
16053
|
-
var
|
|
16054
|
-
var
|
|
16409
|
+
var import_chalk45 = __toESM(require("chalk"));
|
|
16410
|
+
var import_ora16 = __toESM(require("ora"));
|
|
16055
16411
|
init_auth();
|
|
16056
16412
|
init_api();
|
|
16057
16413
|
init_create();
|
|
@@ -16075,15 +16431,15 @@ async function runRule(projectArg, ruleText, opts) {
|
|
|
16075
16431
|
if (opts.off) {
|
|
16076
16432
|
const { status: status3, data: data2 } = await agentCall(`/projects/${projectId}/${apiVersion}`, "PATCH", { authorization: { enforce_authorization: false }, tenant });
|
|
16077
16433
|
if (status3 >= 400) throw new Error(`Could not turn enforcement off (${status3}): ${data2?.error ?? ""}`);
|
|
16078
|
-
console.log(
|
|
16079
|
-
console.log(
|
|
16434
|
+
console.log(import_chalk45.default.green(" \u2713 Enforcement OFF \u2014 the rules are kept (shadow mode)."));
|
|
16435
|
+
console.log(import_chalk45.default.dim(" Turn it back on: ") + import_chalk45.default.cyan(`${B.cli} rule ${projectArg} --enforce`));
|
|
16080
16436
|
return;
|
|
16081
16437
|
}
|
|
16082
16438
|
const rule = (ruleText ?? "").trim();
|
|
16083
16439
|
if (!rule) return proposeFromSpecBranch(ctx, opts);
|
|
16084
16440
|
const { ids: sampleIds, total: sampleTotal } = await gatherSampleIds(projectId, apiVersion);
|
|
16085
16441
|
maybeWarnNoTraffic(sampleTotal, projectArg);
|
|
16086
|
-
const spinner = (0,
|
|
16442
|
+
const spinner = (0, import_ora16.default)(sampleIds.length ? `Designing the rule from ${sampleIds.length} traffic sample(s)\u2026` : "Designing the rule\u2026").start();
|
|
16087
16443
|
const messages = [{
|
|
16088
16444
|
role: "user",
|
|
16089
16445
|
content: `Author this authorization rule and generate the COMPLETE proposal (OpenFGA model + per-route rules) NOW for every affected route. Do not ask clarifying questions; make reasonable assumptions and state them briefly. Rule: "${rule}"`
|
|
@@ -16100,18 +16456,18 @@ async function runRule(projectArg, ruleText, opts) {
|
|
|
16100
16456
|
const proposal = data?.proposal;
|
|
16101
16457
|
if (!proposal || !Array.isArray(proposal.routes) || proposal.routes.length === 0) {
|
|
16102
16458
|
spinner.fail("The agent couldn\u2019t turn that into a rule in one shot.");
|
|
16103
|
-
if (data?.reply) console.log(
|
|
16104
|
-
console.log(
|
|
16459
|
+
if (data?.reply) console.log(import_chalk45.default.dim("\n " + String(data.reply).slice(0, 500) + "\n"));
|
|
16460
|
+
console.log(import_chalk45.default.dim(" Try an interactive session for anything nuanced: ") + import_chalk45.default.cyan(`${B.cli} agent authz ${projectArg}`));
|
|
16105
16461
|
return;
|
|
16106
16462
|
}
|
|
16107
16463
|
spinner.succeed(`Rule designed \u2014 ${proposal.routes.length} route(s) affected.`);
|
|
16108
|
-
if (data?.reply) console.log(
|
|
16464
|
+
if (data?.reply) console.log(import_chalk45.default.dim(" " + String(data.reply).slice(0, 240)));
|
|
16109
16465
|
await shadowThenEnforce(ctx, proposal, opts.enforce ? "yes" : "no", `${B.cli} rule ${projectArg} "${rule}" --enforce`);
|
|
16110
16466
|
}
|
|
16111
16467
|
async function proposeFromSpecBranch(ctx, opts) {
|
|
16112
16468
|
const { projectId, apiVersion, projectArg } = ctx;
|
|
16113
16469
|
const interactive2 = !!process.stdin.isTTY;
|
|
16114
|
-
const spinner = (0,
|
|
16470
|
+
const spinner = (0, import_ora16.default)("Reading the spec for things to protect\u2026").start();
|
|
16115
16471
|
const first = await agentCall(`/projects/${projectId}/${apiVersion}/authz/propose`, "POST", {});
|
|
16116
16472
|
if (first.status >= 400) {
|
|
16117
16473
|
spinner.fail(`Could not work out what to protect (${first.status}): ${first.data?.details ?? first.data?.error ?? ""}`);
|
|
@@ -16121,36 +16477,36 @@ async function proposeFromSpecBranch(ctx, opts) {
|
|
|
16121
16477
|
const protectable = families.filter((f2) => !f2.skipped_reason);
|
|
16122
16478
|
if (!protectable.length) {
|
|
16123
16479
|
spinner.stop();
|
|
16124
|
-
console.log(
|
|
16125
|
-
console.log(
|
|
16480
|
+
console.log(import_chalk45.default.dim(` Nothing to lock: the spec has no resource with a POST collection plus item routes${families.length ? ` (${families.length} candidate(s) not protectable)` : ""}.`));
|
|
16481
|
+
console.log(import_chalk45.default.dim(" Describe the rule instead: ") + import_chalk45.default.cyan(`${B.cli} rule ${projectArg} "users see only their own rows"`));
|
|
16126
16482
|
return;
|
|
16127
16483
|
}
|
|
16128
16484
|
spinner.stop();
|
|
16129
16485
|
const chosen = interactive2 ? await pickFamilies(families) : protectable.map((f2) => f2.key);
|
|
16130
16486
|
if (!chosen.length) {
|
|
16131
|
-
console.log(
|
|
16487
|
+
console.log(import_chalk45.default.dim(" Nothing selected \u2014 no rules written."));
|
|
16132
16488
|
return;
|
|
16133
16489
|
}
|
|
16134
16490
|
let data = first.data;
|
|
16135
16491
|
if (chosen.length !== protectable.length) {
|
|
16136
16492
|
const second = await agentCall(`/projects/${projectId}/${apiVersion}/authz/propose`, "POST", { families: chosen });
|
|
16137
16493
|
if (second.status >= 400) {
|
|
16138
|
-
console.log(
|
|
16494
|
+
console.log(import_chalk45.default.red(` Could not build the rules for that selection (${second.status}): ${second.data?.error ?? ""}`));
|
|
16139
16495
|
return;
|
|
16140
16496
|
}
|
|
16141
16497
|
data = second.data;
|
|
16142
16498
|
}
|
|
16143
16499
|
const proposal = data?.proposal;
|
|
16144
16500
|
if (!proposal || !Array.isArray(proposal.routes) || !proposal.routes.length) {
|
|
16145
|
-
console.log(
|
|
16501
|
+
console.log(import_chalk45.default.red(" The authorization service returned no routes for that selection."));
|
|
16146
16502
|
return;
|
|
16147
16503
|
}
|
|
16148
16504
|
const sim = data?.simulation;
|
|
16149
16505
|
if (sim?.blocked_without_backfill) {
|
|
16150
|
-
console.log(
|
|
16151
|
-
console.log(
|
|
16506
|
+
console.log(import_chalk45.default.yellow(` \u26A0 ${sim.blocked_without_backfill} request(s) that succeed today would be denied once enforced \u2014`));
|
|
16507
|
+
console.log(import_chalk45.default.yellow(" rows created before the rules have no recorded owner. Backfill them or accept it."));
|
|
16152
16508
|
}
|
|
16153
|
-
console.log(
|
|
16509
|
+
console.log(import_chalk45.default.green(` \u2713 ${chosen.map((k) => k.replace(/^\//, "")).join(" + ")} \u2014 ${proposal.routes.length} route rule(s).`));
|
|
16154
16510
|
let enforce = opts.enforce ? "yes" : interactive2 ? "ask" : "no";
|
|
16155
16511
|
await shadowThenEnforce(ctx, proposal, enforce, `${B.cli} rule ${projectArg} --enforce`);
|
|
16156
16512
|
}
|
|
@@ -16158,37 +16514,37 @@ async function shadowThenEnforce(ctx, proposal, enforce, enforceCmd) {
|
|
|
16158
16514
|
const { projectId, apiVersion, tenant, projectArg } = ctx;
|
|
16159
16515
|
const pub = await applyProposal({ projectId, apiVersion, tenant, proposal, enable: false });
|
|
16160
16516
|
if (pub.aborted) {
|
|
16161
|
-
console.log(
|
|
16517
|
+
console.log(import_chalk45.default.red(` Nothing saved${pub.ok ? ` beyond ${pub.ok} route(s)` : ""} \u2014 fix the error above and re-run.`));
|
|
16162
16518
|
return;
|
|
16163
16519
|
}
|
|
16164
|
-
console.log(
|
|
16520
|
+
console.log(import_chalk45.default.green(` \u2713 Saved in shadow mode \u2014 model + ${pub.ok} route(s). Nothing is blocked yet.`));
|
|
16165
16521
|
if (enforce === "ask") {
|
|
16166
16522
|
const { default: inquirer3 } = await import("inquirer");
|
|
16167
16523
|
const { on } = await inquirer3.prompt([{ type: "confirm", name: "on", message: "Turn enforcement on now?", default: true }]);
|
|
16168
16524
|
enforce = on ? "yes" : "no";
|
|
16169
16525
|
}
|
|
16170
16526
|
if (enforce !== "yes") {
|
|
16171
|
-
console.log(
|
|
16527
|
+
console.log(import_chalk45.default.dim(" Review it (shadow logs), then enforce: ") + import_chalk45.default.cyan(enforceCmd));
|
|
16172
16528
|
return;
|
|
16173
16529
|
}
|
|
16174
16530
|
const enf = await applyProposal({ projectId, apiVersion, tenant, proposal, enable: true });
|
|
16175
16531
|
if (enf.aborted) {
|
|
16176
|
-
console.log(
|
|
16532
|
+
console.log(import_chalk45.default.red(` Enforcement NOT on \u2014 ${enf.fail} route rule(s) failed (listed above). Rules stay in shadow mode.`));
|
|
16177
16533
|
return;
|
|
16178
16534
|
}
|
|
16179
16535
|
if (!enf.enabled) {
|
|
16180
|
-
console.log(
|
|
16536
|
+
console.log(import_chalk45.default.red(" Enforcement NOT on \u2014 the routes are set but the project switch did not flip (see above)."));
|
|
16181
16537
|
return;
|
|
16182
16538
|
}
|
|
16183
|
-
console.log(
|
|
16184
|
-
console.log(
|
|
16185
|
-
console.log(
|
|
16186
|
-
console.log(
|
|
16187
|
-
console.log(
|
|
16539
|
+
console.log(import_chalk45.default.green(` \u2713 Enforcement ON \u2014 ${enf.ok} route(s) + project switch on.`));
|
|
16540
|
+
console.log(import_chalk45.default.dim(" Route config takes up to ~90 s to reach every edge."));
|
|
16541
|
+
console.log(import_chalk45.default.dim(" Testing a locked route? Use the sign-in door, or send X-End-User-Id / a key minted with --for \u2014"));
|
|
16542
|
+
console.log(import_chalk45.default.dim(" a bare key has no person to check."));
|
|
16543
|
+
console.log(import_chalk45.default.dim(` Undo: `) + import_chalk45.default.cyan(B.cli === "mcpblaze" ? `${B.cli} rule ${projectArg} --off` : `${B.cli} config ${projectArg} authorization.enforce_authorization false`));
|
|
16188
16544
|
}
|
|
16189
16545
|
|
|
16190
16546
|
// src/commands/openapi.ts
|
|
16191
|
-
var
|
|
16547
|
+
var import_chalk46 = __toESM(require("chalk"));
|
|
16192
16548
|
init_auth();
|
|
16193
16549
|
init_api();
|
|
16194
16550
|
init_agent_chat();
|
|
@@ -16200,10 +16556,10 @@ async function runOpenapi(projectArg, apiVersionArg) {
|
|
|
16200
16556
|
if (!match) throw new Error(`Project "${projectArg}" not found in your active team. Run \`apiblaze projects\`.`);
|
|
16201
16557
|
const projectId = match.projectId;
|
|
16202
16558
|
const apiVersion = apiVersionArg || match.apiVersion;
|
|
16203
|
-
console.log(
|
|
16559
|
+
console.log(import_chalk46.default.dim("Gathering captured traffic samples\u2026"));
|
|
16204
16560
|
const routesRes = await agentCall(`/projects/${projectId}/${apiVersion}/samples/routes`, "GET");
|
|
16205
16561
|
if (routesRes.status >= 400) {
|
|
16206
|
-
console.log(
|
|
16562
|
+
console.log(import_chalk46.default.red(`Could not list traffic (${routesRes.status}): ${routesRes.data?.error ?? ""}`));
|
|
16207
16563
|
return;
|
|
16208
16564
|
}
|
|
16209
16565
|
const routes = routesRes.data?.routes ?? (Array.isArray(routesRes.data) ? routesRes.data : []);
|
|
@@ -16216,25 +16572,25 @@ async function runOpenapi(projectArg, apiVersionArg) {
|
|
|
16216
16572
|
}
|
|
16217
16573
|
const sampleIds = [...ids];
|
|
16218
16574
|
if (sampleIds.length === 0) {
|
|
16219
|
-
console.log(
|
|
16575
|
+
console.log(import_chalk46.default.yellow("No captured traffic samples found. Hit the dev environment of this proxy to capture some traces, then retry."));
|
|
16220
16576
|
return;
|
|
16221
16577
|
}
|
|
16222
16578
|
async function publish(ctx) {
|
|
16223
16579
|
const patch = ctx.proposal.patch;
|
|
16224
16580
|
if (!Array.isArray(patch) || patch.length === 0) {
|
|
16225
|
-
ctx.log(
|
|
16581
|
+
ctx.log(import_chalk46.default.green(" Nothing to publish \u2014 the spec already covers the observed traffic.\n"));
|
|
16226
16582
|
return;
|
|
16227
16583
|
}
|
|
16228
16584
|
const specSource = ctx.lastData?.spec_source ?? "unknown";
|
|
16229
16585
|
const endpoint = specSource === "github" ? "open-pr" : "publish-openapi";
|
|
16230
16586
|
const pub = await agentCall(`/projects/${projectId}/${apiVersion}/samples/${endpoint}`, "POST", { patch, sample_ids_used: sampleIds });
|
|
16231
16587
|
if (pub.status >= 400) {
|
|
16232
|
-
ctx.log(
|
|
16588
|
+
ctx.log(import_chalk46.default.red(` Publish failed (${pub.status}): ${pub.data?.error ?? ""}
|
|
16233
16589
|
`));
|
|
16234
16590
|
return;
|
|
16235
16591
|
}
|
|
16236
16592
|
const prUrl = pub.data?.pr_url;
|
|
16237
|
-
ctx.log(
|
|
16593
|
+
ctx.log(import_chalk46.default.green(prUrl ? ` \u2713 Pull request opened: ${prUrl}
|
|
16238
16594
|
` : " \u2713 Published updated OpenAPI spec.\n"));
|
|
16239
16595
|
}
|
|
16240
16596
|
await runAgentChatRepl({
|
|
@@ -16258,13 +16614,13 @@ async function runOpenapi(projectArg, apiVersionArg) {
|
|
|
16258
16614
|
init_mcp();
|
|
16259
16615
|
|
|
16260
16616
|
// src/commands/delete.ts
|
|
16261
|
-
var
|
|
16262
|
-
var
|
|
16617
|
+
var import_chalk48 = __toESM(require("chalk"));
|
|
16618
|
+
var import_ora17 = __toESM(require("ora"));
|
|
16263
16619
|
init_admin();
|
|
16264
16620
|
init_resolve();
|
|
16265
16621
|
|
|
16266
16622
|
// src/lib/job.ts
|
|
16267
|
-
var
|
|
16623
|
+
var import_chalk47 = __toESM(require("chalk"));
|
|
16268
16624
|
init_admin();
|
|
16269
16625
|
var POLL_MS = 3e3;
|
|
16270
16626
|
var MAX_WAIT_MS = 30 * 6e4;
|
|
@@ -16298,7 +16654,7 @@ async function pollJob(jobId, opts = {}) {
|
|
|
16298
16654
|
const started = Date.now();
|
|
16299
16655
|
const path20 = jobPath(jobId, opts.teamId);
|
|
16300
16656
|
let lastPhase = "";
|
|
16301
|
-
if (!opts.quiet) process.stdout.write(
|
|
16657
|
+
if (!opts.quiet) process.stdout.write(import_chalk47.default.dim(opts.label ?? "Deleting"));
|
|
16302
16658
|
for (; ; ) {
|
|
16303
16659
|
if (Date.now() - started > MAX_WAIT_MS) {
|
|
16304
16660
|
if (!opts.quiet) process.stdout.write("\n");
|
|
@@ -16312,11 +16668,11 @@ async function pollJob(jobId, opts = {}) {
|
|
|
16312
16668
|
if (!opts.quiet) {
|
|
16313
16669
|
const phase = describeJob(job);
|
|
16314
16670
|
if (phase !== lastPhase) {
|
|
16315
|
-
process.stdout.write(
|
|
16671
|
+
process.stdout.write(import_chalk47.default.dim(`
|
|
16316
16672
|
${phase}`));
|
|
16317
16673
|
lastPhase = phase;
|
|
16318
16674
|
}
|
|
16319
|
-
process.stdout.write(
|
|
16675
|
+
process.stdout.write(import_chalk47.default.dim("."));
|
|
16320
16676
|
}
|
|
16321
16677
|
if (job.status === "done" || job.status === "failed") {
|
|
16322
16678
|
if (!opts.quiet) process.stdout.write("\n");
|
|
@@ -16329,7 +16685,7 @@ async function settleDelete(accepted, opts) {
|
|
|
16329
16685
|
if (!jobId) return null;
|
|
16330
16686
|
if (opts.noWait) {
|
|
16331
16687
|
if (!opts.json) {
|
|
16332
|
-
console.log(
|
|
16688
|
+
console.log(import_chalk47.default.dim(`Queued as job ${jobId}. Check it with: apiblaze job ${jobId}${opts.teamId ? ` --team ${opts.teamId}` : ""}`));
|
|
16333
16689
|
}
|
|
16334
16690
|
return null;
|
|
16335
16691
|
}
|
|
@@ -16350,7 +16706,7 @@ async function settleDelete(accepted, opts) {
|
|
|
16350
16706
|
async function runDelete(project, version2, opts) {
|
|
16351
16707
|
const { teamId } = await resolveTeam(opts.team);
|
|
16352
16708
|
const proj2 = await resolveProject(teamId, project, version2);
|
|
16353
|
-
const spinner = (0,
|
|
16709
|
+
const spinner = (0, import_ora17.default)("Checking delete impact...").start();
|
|
16354
16710
|
let impact;
|
|
16355
16711
|
try {
|
|
16356
16712
|
impact = await admin({
|
|
@@ -16369,10 +16725,10 @@ async function runDelete(project, version2, opts) {
|
|
|
16369
16725
|
return;
|
|
16370
16726
|
}
|
|
16371
16727
|
} else {
|
|
16372
|
-
console.log(`${
|
|
16728
|
+
console.log(`${import_chalk48.default.red.bold("Delete")} ${import_chalk48.default.bold(proj2.projectName)} ${import_chalk48.default.dim("v" + proj2.apiVersion)} ${import_chalk48.default.dim("(" + proj2.projectId + ")")}`);
|
|
16373
16729
|
if (impact && typeof impact === "object") {
|
|
16374
16730
|
const counts = impact.counts ?? impact.impact ?? impact;
|
|
16375
|
-
console.log(
|
|
16731
|
+
console.log(import_chalk48.default.dim(" This removes (cascade): ") + import_chalk48.default.yellow(JSON.stringify(counts)));
|
|
16376
16732
|
}
|
|
16377
16733
|
if (!opts.yes) {
|
|
16378
16734
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -16380,12 +16736,12 @@ async function runDelete(project, version2, opts) {
|
|
|
16380
16736
|
{ type: "confirm", name: "confirm", message: `Permanently delete ${proj2.projectName} v${proj2.apiVersion}? This cannot be undone.`, default: false }
|
|
16381
16737
|
]);
|
|
16382
16738
|
if (!confirm3) {
|
|
16383
|
-
console.log(
|
|
16739
|
+
console.log(import_chalk48.default.dim("Aborted."));
|
|
16384
16740
|
return;
|
|
16385
16741
|
}
|
|
16386
16742
|
}
|
|
16387
16743
|
}
|
|
16388
|
-
const s2 = (0,
|
|
16744
|
+
const s2 = (0, import_ora17.default)("Deleting...").start();
|
|
16389
16745
|
let accepted;
|
|
16390
16746
|
try {
|
|
16391
16747
|
accepted = await admin({
|
|
@@ -16406,7 +16762,7 @@ async function runDelete(project, version2, opts) {
|
|
|
16406
16762
|
});
|
|
16407
16763
|
if (!opts.json) {
|
|
16408
16764
|
if (job || accepted?.job_id == null) {
|
|
16409
|
-
console.log(
|
|
16765
|
+
console.log(import_chalk48.default.green(`\u2714 Deleted ${proj2.projectName} v${proj2.apiVersion}.`));
|
|
16410
16766
|
}
|
|
16411
16767
|
} else {
|
|
16412
16768
|
console.log(JSON.stringify({
|
|
@@ -16419,7 +16775,7 @@ async function runDelete(project, version2, opts) {
|
|
|
16419
16775
|
}
|
|
16420
16776
|
|
|
16421
16777
|
// src/commands/job.ts
|
|
16422
|
-
var
|
|
16778
|
+
var import_chalk49 = __toESM(require("chalk"));
|
|
16423
16779
|
init_admin();
|
|
16424
16780
|
init_resolve();
|
|
16425
16781
|
async function runJobStatus(jobId, opts) {
|
|
@@ -16435,24 +16791,24 @@ async function runJobStatus(jobId, opts) {
|
|
|
16435
16791
|
if (job.status === "failed") process.exitCode = 1;
|
|
16436
16792
|
return;
|
|
16437
16793
|
}
|
|
16438
|
-
const colour = job.status === "done" ?
|
|
16439
|
-
console.log(`${
|
|
16794
|
+
const colour = job.status === "done" ? import_chalk49.default.green : job.status === "failed" ? import_chalk49.default.red : import_chalk49.default.yellow;
|
|
16795
|
+
console.log(`${import_chalk49.default.bold(job.job_id)} ${import_chalk49.default.dim(`(${job.kind})`)} ${colour.bold(job.status)}`);
|
|
16440
16796
|
if (job.status === "queued" || job.status === "running") {
|
|
16441
|
-
console.log(
|
|
16797
|
+
console.log(import_chalk49.default.dim(` ${describeJob(job)}`));
|
|
16442
16798
|
}
|
|
16443
16799
|
const counts = [];
|
|
16444
16800
|
if (job.progress?.proxies) counts.push(`${job.progress.proxies} prox`);
|
|
16445
16801
|
if (job.progress?.tenants) counts.push(`${job.progress.tenants} tenant(s)`);
|
|
16446
|
-
if (counts.length) console.log(
|
|
16447
|
-
if (job.attempts > 1) console.log(
|
|
16448
|
-
if (job.error) console.log(
|
|
16802
|
+
if (counts.length) console.log(import_chalk49.default.dim(` removed: ${counts.join(", ")}`));
|
|
16803
|
+
if (job.attempts > 1) console.log(import_chalk49.default.dim(` attempts: ${job.attempts}`));
|
|
16804
|
+
if (job.error) console.log(import_chalk49.default.red(` ${job.error}`));
|
|
16449
16805
|
const parked = job.progress?.failed_units ?? [];
|
|
16450
|
-
if (parked.length) console.log(
|
|
16806
|
+
if (parked.length) console.log(import_chalk49.default.red(` left behind: ${parked.slice(0, 10).join(", ")}`));
|
|
16451
16807
|
if (job.status === "failed") process.exitCode = 1;
|
|
16452
16808
|
}
|
|
16453
16809
|
|
|
16454
16810
|
// src/commands/export.ts
|
|
16455
|
-
var
|
|
16811
|
+
var import_chalk50 = __toESM(require("chalk"));
|
|
16456
16812
|
var import_fs13 = require("fs");
|
|
16457
16813
|
init_admin();
|
|
16458
16814
|
init_auth();
|
|
@@ -16471,7 +16827,7 @@ async function runExport(projectArg, versionArg, opts) {
|
|
|
16471
16827
|
if (opts.noConsumers) q.set("include_consumers", "0");
|
|
16472
16828
|
if (opts.keys) q.set("keys", opts.keys);
|
|
16473
16829
|
const qs = q.toString() ? `?${q.toString()}` : "";
|
|
16474
|
-
console.log(
|
|
16830
|
+
console.log(import_chalk50.default.dim(`Queuing ${target2} export of ${projectId}@${version2}\u2026`));
|
|
16475
16831
|
const job = await admin({
|
|
16476
16832
|
path: `/projects/${projectId}/${version2}/export/${target2}${qs}`,
|
|
16477
16833
|
method: "GET",
|
|
@@ -16481,11 +16837,11 @@ async function runExport(projectArg, versionArg, opts) {
|
|
|
16481
16837
|
const statusPath = `/projects/${projectId}/${version2}/export/jobs/${job.job_id}`;
|
|
16482
16838
|
let status2 = "queued";
|
|
16483
16839
|
const started = Date.now();
|
|
16484
|
-
process.stdout.write(
|
|
16840
|
+
process.stdout.write(import_chalk50.default.dim("Building bundle"));
|
|
16485
16841
|
while (status2 !== "ready" && status2 !== "failed") {
|
|
16486
16842
|
if (Date.now() - started > 10 * 6e4) throw new Error("Export timed out after 10 minutes.");
|
|
16487
16843
|
await new Promise((r) => setTimeout(r, 3e3));
|
|
16488
|
-
process.stdout.write(
|
|
16844
|
+
process.stdout.write(import_chalk50.default.dim("."));
|
|
16489
16845
|
const st = await admin({ path: statusPath, method: "GET", summary: "poll export" });
|
|
16490
16846
|
status2 = st?.status ?? "queued";
|
|
16491
16847
|
if (status2 === "failed") {
|
|
@@ -16503,15 +16859,15 @@ async function runExport(projectArg, versionArg, opts) {
|
|
|
16503
16859
|
if (!res.ok) throw new Error(`Download failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
16504
16860
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
16505
16861
|
(0, import_fs13.writeFileSync)(outFile, buf);
|
|
16506
|
-
console.log(
|
|
16507
|
-
if (job.decryption_key) console.log(
|
|
16508
|
-
console.log(
|
|
16509
|
-
if (target2 === "kong") console.log(
|
|
16862
|
+
console.log(import_chalk50.default.green(`\u2713 Saved ${outFile} (${(buf.length / 1024).toFixed(0)} KB)`));
|
|
16863
|
+
if (job.decryption_key) console.log(import_chalk50.default.yellow(`Decryption key (shown once): ${job.decryption_key}`));
|
|
16864
|
+
console.log(import_chalk50.default.dim("Read MIGRATION-REPORT.json inside the zip for the full fidelity ledger."));
|
|
16865
|
+
if (target2 === "kong") console.log(import_chalk50.default.dim("Kong: unzip, then `cd kong && docker compose up` (see kong/README.md)."));
|
|
16510
16866
|
}
|
|
16511
16867
|
|
|
16512
16868
|
// src/commands/recipe-search.ts
|
|
16513
|
-
var
|
|
16514
|
-
var
|
|
16869
|
+
var import_chalk51 = __toESM(require("chalk"));
|
|
16870
|
+
var import_ora18 = __toESM(require("ora"));
|
|
16515
16871
|
init_caller();
|
|
16516
16872
|
|
|
16517
16873
|
// src/lib/recipes.ts
|
|
@@ -16697,7 +17053,7 @@ function upstreamHosts(recipe) {
|
|
|
16697
17053
|
|
|
16698
17054
|
// src/commands/recipe-search.ts
|
|
16699
17055
|
async function runRecipeSearch(query, opts) {
|
|
16700
|
-
const spinner = !opts.json ? (0,
|
|
17056
|
+
const spinner = !opts.json ? (0, import_ora18.default)("Searching recipes...").start() : null;
|
|
16701
17057
|
let hits;
|
|
16702
17058
|
try {
|
|
16703
17059
|
hits = await searchRecipes(query ?? "", opts.limit ? Number(opts.limit) : void 0);
|
|
@@ -16711,20 +17067,20 @@ async function runRecipeSearch(query, opts) {
|
|
|
16711
17067
|
return;
|
|
16712
17068
|
}
|
|
16713
17069
|
if (!hits.length) {
|
|
16714
|
-
console.log(
|
|
16715
|
-
console.log(
|
|
17070
|
+
console.log(import_chalk51.default.yellow(query ? `No public recipes match "${query}".` : "No public recipes yet."));
|
|
17071
|
+
console.log(import_chalk51.default.dim(" Publish one with: apiblaze publish <proxy> --as @yourhandle/name"));
|
|
16716
17072
|
return;
|
|
16717
17073
|
}
|
|
16718
17074
|
console.log();
|
|
16719
17075
|
for (const h of hits) {
|
|
16720
|
-
const pin = h.latest_revision ?
|
|
16721
|
-
const installs = h.install_count ?
|
|
16722
|
-
console.log(` ${
|
|
16723
|
-
if (h.summary) console.log(` ${
|
|
17076
|
+
const pin = h.latest_revision ? import_chalk51.default.dim(`@${h.latest_revision}`) : "";
|
|
17077
|
+
const installs = h.install_count ? import_chalk51.default.dim(` ${h.install_count} install${h.install_count === 1 ? "" : "s"}`) : "";
|
|
17078
|
+
console.log(` ${import_chalk51.default.bold(h.name)}${pin}${h.display_name ? import_chalk51.default.dim(` \u2014 ${h.display_name}`) : ""}${installs}`);
|
|
17079
|
+
if (h.summary) console.log(` ${import_chalk51.default.dim(h.summary)}`);
|
|
16724
17080
|
}
|
|
16725
17081
|
console.log();
|
|
16726
|
-
console.log(
|
|
16727
|
-
console.log(
|
|
17082
|
+
console.log(import_chalk51.default.dim(` Install one with: ${import_chalk51.default.cyan(`npx apiblaze install ${hits[0].name}`)}`));
|
|
17083
|
+
console.log(import_chalk51.default.dim(` Read one first: ${import_chalk51.default.cyan(`npx apiblaze show ${hits[0].name}`)}`));
|
|
16728
17084
|
console.log();
|
|
16729
17085
|
}
|
|
16730
17086
|
async function runRecipeShow(nameArg, opts) {
|
|
@@ -16732,7 +17088,7 @@ async function runRecipeShow(nameArg, opts) {
|
|
|
16732
17088
|
const wantsSection = !!(opts.transforms || opts.settings || opts.auth || opts.openapi);
|
|
16733
17089
|
const caller = resolveCaller();
|
|
16734
17090
|
const head = ref.revision === void 0 ? await getRecipeHead(ref).catch(() => null) : null;
|
|
16735
|
-
const spinner = !opts.json ? (0,
|
|
17091
|
+
const spinner = !opts.json ? (0, import_ora18.default)(`Reading ${ref.name}...`).start() : null;
|
|
16736
17092
|
let recipe;
|
|
16737
17093
|
try {
|
|
16738
17094
|
recipe = await fetchRecipeFile(ref, caller);
|
|
@@ -16750,8 +17106,8 @@ async function runRecipeShow(nameArg, opts) {
|
|
|
16750
17106
|
return;
|
|
16751
17107
|
}
|
|
16752
17108
|
console.log();
|
|
16753
|
-
console.log(`${
|
|
16754
|
-
if (recipe.display_name) console.log(` ${
|
|
17109
|
+
console.log(`${import_chalk51.default.bold(`${recipe.name}@${recipe.revision}`)}${import_chalk51.default.dim(` \u2014 by ${ref.handle}`)}`);
|
|
17110
|
+
if (recipe.display_name) console.log(` ${import_chalk51.default.bold(recipe.display_name)}`);
|
|
16755
17111
|
if (recipe.summary) console.log(` ${recipe.summary}`);
|
|
16756
17112
|
const meta = [
|
|
16757
17113
|
head?.install_count !== void 0 ? `${head.install_count} install${head.install_count === 1 ? "" : "s"}` : null,
|
|
@@ -16759,7 +17115,7 @@ async function runRecipeShow(nameArg, opts) {
|
|
|
16759
17115
|
recipe.published_at ? `published ${recipe.published_at.slice(0, 10)}` : null,
|
|
16760
17116
|
`api version ${recipe.api_version}`
|
|
16761
17117
|
].filter(Boolean).join(" \xB7 ");
|
|
16762
|
-
console.log(
|
|
17118
|
+
console.log(import_chalk51.default.dim(` ${meta}`));
|
|
16763
17119
|
console.log();
|
|
16764
17120
|
if (wantsSection) {
|
|
16765
17121
|
if (opts.settings) printSection("settings", recipe.settings);
|
|
@@ -16773,34 +17129,34 @@ async function runRecipeShow(nameArg, opts) {
|
|
|
16773
17129
|
const rules = recipe.transformations?.rules?.length ?? 0;
|
|
16774
17130
|
const tables = recipe.transformations?.mapping_tables?.length ?? 0;
|
|
16775
17131
|
const keyTypes = recipe.auth?.key_types?.length ?? 0;
|
|
16776
|
-
if (hosts.length) console.log(` ${
|
|
16777
|
-
console.log(` ${
|
|
16778
|
-
console.log(` ${
|
|
17132
|
+
if (hosts.length) console.log(` ${import_chalk51.default.dim("sends requests to")} ${hosts.join(", ")}`);
|
|
17133
|
+
console.log(` ${import_chalk51.default.dim("creates ")} ${describeCreates(keyTypes, routes, !!recipe.settings.mcp_enabled)}`);
|
|
17134
|
+
console.log(` ${import_chalk51.default.dim("asks you for ")} ${recipe.questions.length} value${recipe.questions.length === 1 ? "" : "s"}`);
|
|
16779
17135
|
if (rules || tables) {
|
|
16780
|
-
console.log(` ${
|
|
17136
|
+
console.log(` ${import_chalk51.default.dim("transforms ")} ${rules} rule${rules === 1 ? "" : "s"}${tables ? `, ${tables} mapping table${tables === 1 ? "" : "s"}` : ""}`);
|
|
16781
17137
|
}
|
|
16782
17138
|
if (recipe.settings.publish_openapi === true) {
|
|
16783
|
-
console.log(` ${
|
|
17139
|
+
console.log(` ${import_chalk51.default.dim("makes public ")} this API's spec`);
|
|
16784
17140
|
}
|
|
16785
17141
|
if (recipe.questions.length) {
|
|
16786
17142
|
console.log();
|
|
16787
|
-
console.log(
|
|
17143
|
+
console.log(import_chalk51.default.bold(" It will ask you for"));
|
|
16788
17144
|
for (const q of recipe.questions) {
|
|
16789
|
-
console.log(` ${q.prompt}${q.secret ?
|
|
16790
|
-
if (q.example) console.log(
|
|
17145
|
+
console.log(` ${q.prompt}${q.secret ? import_chalk51.default.dim(" (hidden while you type)") : ""}`);
|
|
17146
|
+
if (q.example) console.log(import_chalk51.default.dim(` e.g. ${q.example}`));
|
|
16791
17147
|
}
|
|
16792
17148
|
}
|
|
16793
17149
|
if (recipe.requires_confirmation?.length) {
|
|
16794
17150
|
console.log();
|
|
16795
|
-
console.log(
|
|
17151
|
+
console.log(import_chalk51.default.yellow(" Needs your explicit consent at install time"));
|
|
16796
17152
|
for (const c of recipe.requires_confirmation) {
|
|
16797
|
-
console.log(` ${
|
|
16798
|
-
console.log(
|
|
17153
|
+
console.log(` ${import_chalk51.default.bold(c.kind)} ${c.value}`);
|
|
17154
|
+
console.log(import_chalk51.default.dim(` ${c.why}`));
|
|
16799
17155
|
}
|
|
16800
17156
|
}
|
|
16801
17157
|
console.log();
|
|
16802
|
-
console.log(
|
|
16803
|
-
console.log(
|
|
17158
|
+
console.log(import_chalk51.default.dim(` Install: ${import_chalk51.default.cyan(`npx apiblaze install ${recipe.name}`)}`));
|
|
17159
|
+
console.log(import_chalk51.default.dim(" Sections: --settings --auth --transforms --openapi"));
|
|
16804
17160
|
console.log();
|
|
16805
17161
|
}
|
|
16806
17162
|
function describeCreates(keyTypes, routes, mcp, includeProxy = true) {
|
|
@@ -16810,45 +17166,45 @@ function describeCreates(keyTypes, routes, mcp, includeProxy = true) {
|
|
|
16810
17166
|
return parts.length ? parts.join(", ") : "nothing else";
|
|
16811
17167
|
}
|
|
16812
17168
|
function printSection(title, value) {
|
|
16813
|
-
console.log(
|
|
17169
|
+
console.log(import_chalk51.default.bold(` ${title}`));
|
|
16814
17170
|
console.log(indent(JSON.stringify(value ?? null, null, 2), " "));
|
|
16815
17171
|
console.log();
|
|
16816
17172
|
}
|
|
16817
17173
|
function printAuth(recipe) {
|
|
16818
17174
|
const a = recipe.auth ?? { app_client: null, provider: null, key_types: [] };
|
|
16819
|
-
console.log(
|
|
17175
|
+
console.log(import_chalk51.default.bold(" auth"));
|
|
16820
17176
|
if (a.provider) {
|
|
16821
|
-
console.log(` ${
|
|
16822
|
-
if (a.provider.scopes) console.log(` ${
|
|
16823
|
-
if (a.provider.target_server_token) console.log(` ${
|
|
16824
|
-
console.log(a.provider.managed === true ?
|
|
17177
|
+
console.log(` ${import_chalk51.default.dim("provider ")} ${a.provider.type ?? "(unknown)"}`);
|
|
17178
|
+
if (a.provider.scopes) console.log(` ${import_chalk51.default.dim("scopes ")} ${a.provider.scopes}`);
|
|
17179
|
+
if (a.provider.target_server_token) console.log(` ${import_chalk51.default.dim("sends to API")} ${a.provider.target_server_token}`);
|
|
17180
|
+
console.log(a.provider.managed === true ? import_chalk51.default.dim(" (APIblaze's own GitHub app \u2014 installers get the same zero-config door, nothing to create)") : import_chalk51.default.dim(" (no credentials travel in a recipe \u2014 install asks for your own OAuth app)"));
|
|
16825
17181
|
} else {
|
|
16826
|
-
console.log(
|
|
17182
|
+
console.log(import_chalk51.default.dim(" no login provider \u2014 consumers call it with an API key"));
|
|
16827
17183
|
}
|
|
16828
17184
|
if (a.app_client) {
|
|
16829
|
-
console.log(` ${
|
|
17185
|
+
console.log(` ${import_chalk51.default.dim("login app ")} ${a.app_client.name ?? "(unnamed)"}`);
|
|
16830
17186
|
}
|
|
16831
17187
|
if (a.key_types?.length) {
|
|
16832
|
-
console.log(` ${
|
|
17188
|
+
console.log(` ${import_chalk51.default.dim("key types ")} ${a.key_types.map((k) => String(k.name)).join(", ")}`);
|
|
16833
17189
|
}
|
|
16834
17190
|
console.log();
|
|
16835
17191
|
}
|
|
16836
17192
|
function printTransforms(recipe) {
|
|
16837
17193
|
const rules = recipe.transformations?.rules ?? [];
|
|
16838
17194
|
const tables = recipe.transformations?.mapping_tables ?? [];
|
|
16839
|
-
console.log(
|
|
17195
|
+
console.log(import_chalk51.default.bold(` transforms (${rules.length})`));
|
|
16840
17196
|
for (const r of rules) {
|
|
16841
|
-
const off = r.enabled === false ?
|
|
16842
|
-
console.log(` ${
|
|
17197
|
+
const off = r.enabled === false ? import_chalk51.default.dim(" (disabled)") : "";
|
|
17198
|
+
console.log(` ${import_chalk51.default.bold(String(r.name ?? "(unnamed)"))} ${import_chalk51.default.dim(String(r.phase ?? "request"))}${off}`);
|
|
16843
17199
|
console.log(indent(JSON.stringify(r.action ?? null, null, 2), " "));
|
|
16844
17200
|
if (r.condition) console.log(indent(`when ${JSON.stringify(r.condition)}`, " "));
|
|
16845
17201
|
}
|
|
16846
17202
|
if (tables.length) {
|
|
16847
17203
|
console.log();
|
|
16848
|
-
console.log(
|
|
17204
|
+
console.log(import_chalk51.default.bold(` mapping tables (${tables.length})`));
|
|
16849
17205
|
for (const t of tables) {
|
|
16850
17206
|
const entries2 = Array.isArray(t.entries) ? t.entries.length : 0;
|
|
16851
|
-
console.log(` ${
|
|
17207
|
+
console.log(` ${import_chalk51.default.bold(String(t.name ?? "(unnamed)"))} ${import_chalk51.default.dim(`${entries2} entr${entries2 === 1 ? "y" : "ies"}`)}`);
|
|
16852
17208
|
}
|
|
16853
17209
|
}
|
|
16854
17210
|
console.log();
|
|
@@ -16858,7 +17214,7 @@ function indent(text, pad) {
|
|
|
16858
17214
|
}
|
|
16859
17215
|
|
|
16860
17216
|
// src/commands/recipe-install.ts
|
|
16861
|
-
var
|
|
17217
|
+
var import_chalk52 = __toESM(require("chalk"));
|
|
16862
17218
|
init_types();
|
|
16863
17219
|
init_caller();
|
|
16864
17220
|
init_api();
|
|
@@ -16875,7 +17231,7 @@ async function runRecipeInstall(nameArg, opts) {
|
|
|
16875
17231
|
const chosenTeamId = picked.chosen ? picked.teamId : void 0;
|
|
16876
17232
|
const teamName = picked.teamName;
|
|
16877
17233
|
console.log();
|
|
16878
|
-
console.log(`${
|
|
17234
|
+
console.log(`${import_chalk52.default.bold(pinned)}${import_chalk52.default.dim(` \u2014 by ${ref.handle}`)}`);
|
|
16879
17235
|
if (recipe.summary) console.log(` ${recipe.summary}`);
|
|
16880
17236
|
console.log();
|
|
16881
17237
|
let project;
|
|
@@ -16899,7 +17255,7 @@ async function runRecipeInstall(nameArg, opts) {
|
|
|
16899
17255
|
reuseTenant = chosenTenant.existing;
|
|
16900
17256
|
printSummary(recipe, project, tenant, reuseTenant);
|
|
16901
17257
|
if (!assumeYes && !await confirmYesNo(" continue?", interactive2)) {
|
|
16902
|
-
console.log(
|
|
17258
|
+
console.log(import_chalk52.default.yellow(" Cancelled \u2014 nothing was created."));
|
|
16903
17259
|
return;
|
|
16904
17260
|
}
|
|
16905
17261
|
}
|
|
@@ -16918,18 +17274,18 @@ async function runRecipeInstall(nameArg, opts) {
|
|
|
16918
17274
|
process.stdout.write(` ${step.label.padEnd(33)}`);
|
|
16919
17275
|
try {
|
|
16920
17276
|
const note = await step.run();
|
|
16921
|
-
console.log(`${
|
|
17277
|
+
console.log(`${import_chalk52.default.green("\u2713")}${note ? ` ${import_chalk52.default.dim(note)}` : ""}`);
|
|
16922
17278
|
} catch (err) {
|
|
16923
|
-
console.log(`${
|
|
17279
|
+
console.log(`${import_chalk52.default.red("\u2717")} ${import_chalk52.default.red(describeError(err))}`);
|
|
16924
17280
|
console.log();
|
|
16925
17281
|
if (step.n === 1) {
|
|
16926
|
-
console.log(
|
|
17282
|
+
console.log(import_chalk52.default.red(`stopped at step 1 of ${steps.length}. Nothing was created.`));
|
|
16927
17283
|
console.log("run the same command again once the problem above is fixed:");
|
|
16928
|
-
console.log(
|
|
17284
|
+
console.log(import_chalk52.default.cyan(` npx apiblaze install ${nameArg}`));
|
|
16929
17285
|
} else {
|
|
16930
|
-
console.log(
|
|
17286
|
+
console.log(import_chalk52.default.red(`stopped at step ${step.n} of ${steps.length}. ${project} exists but is not finished.`));
|
|
16931
17287
|
console.log("continue with:");
|
|
16932
|
-
console.log(
|
|
17288
|
+
console.log(import_chalk52.default.cyan(` npx apiblaze install ${nameArg} --into ${project}`));
|
|
16933
17289
|
}
|
|
16934
17290
|
console.log();
|
|
16935
17291
|
process.exit(1);
|
|
@@ -16939,9 +17295,9 @@ async function runRecipeInstall(nameArg, opts) {
|
|
|
16939
17295
|
const urls = installUrls(ctx);
|
|
16940
17296
|
console.log();
|
|
16941
17297
|
console.log(`done in ${secs}s.`);
|
|
16942
|
-
console.log(` ${
|
|
16943
|
-
if (recipe.settings.mcp_enabled === true) console.log(` ${
|
|
16944
|
-
if (recipe.settings.enable_portal === true) console.log(` ${
|
|
17298
|
+
console.log(` ${import_chalk52.default.dim("proxy ")} ${import_chalk52.default.bold(urls.proxy_url)}`);
|
|
17299
|
+
if (recipe.settings.mcp_enabled === true) console.log(` ${import_chalk52.default.dim("mcp ")} ${import_chalk52.default.bold(urls.mcp_url)}`);
|
|
17300
|
+
if (recipe.settings.enable_portal === true) console.log(` ${import_chalk52.default.dim("portal")} ${import_chalk52.default.bold(urls.portal_url)}`);
|
|
16945
17301
|
console.log();
|
|
16946
17302
|
}
|
|
16947
17303
|
function buildSteps(inp) {
|
|
@@ -17243,7 +17599,7 @@ async function nameIsFree(name, teamId) {
|
|
|
17243
17599
|
async function chooseProxyName(slug, asOpt, teamId, interactive2) {
|
|
17244
17600
|
if (asOpt !== void 0) {
|
|
17245
17601
|
const name = normalizeName3(asOpt);
|
|
17246
|
-
if (name !== asOpt) console.log(
|
|
17602
|
+
if (name !== asOpt) console.log(import_chalk52.default.yellow(` Using "${name}" \u2014 names are lowercase letters and digits only.`));
|
|
17247
17603
|
if (name.length < 3) throw new Error("A proxy name must be at least 3 characters (lowercase letters and digits only).");
|
|
17248
17604
|
const free = await nameIsFree(name, teamId);
|
|
17249
17605
|
if (free === false) throw new Error(`"${name}" is already taken. Pick another with --as.`);
|
|
@@ -17260,7 +17616,7 @@ async function chooseProxyName(slug, asOpt, teamId, interactive2) {
|
|
|
17260
17616
|
}
|
|
17261
17617
|
if (!interactive2) return suggested;
|
|
17262
17618
|
const { default: inquirer3 } = await import("inquirer");
|
|
17263
|
-
console.log(` ${
|
|
17619
|
+
console.log(` ${import_chalk52.default.dim("proxy")} ${slug} \u2192 ${import_chalk52.default.bold(suggested)} ${import_chalk52.default.dim("[enter to accept, or type a name]")}`);
|
|
17264
17620
|
for (; ; ) {
|
|
17265
17621
|
const { raw } = await inquirer3.prompt([{
|
|
17266
17622
|
type: "input",
|
|
@@ -17271,20 +17627,20 @@ async function chooseProxyName(slug, asOpt, teamId, interactive2) {
|
|
|
17271
17627
|
}]);
|
|
17272
17628
|
const name = normalizeName3(raw);
|
|
17273
17629
|
if (name !== String(raw).trim()) {
|
|
17274
|
-
console.log(
|
|
17630
|
+
console.log(import_chalk52.default.yellow(` \u2192 using ${import_chalk52.default.bold(name)} (lowercase letters and digits only)`));
|
|
17275
17631
|
}
|
|
17276
17632
|
if (name.length < 3) {
|
|
17277
|
-
console.log(
|
|
17633
|
+
console.log(import_chalk52.default.yellow(" Name must be at least 3 characters.\n"));
|
|
17278
17634
|
continue;
|
|
17279
17635
|
}
|
|
17280
17636
|
const free = await nameIsFree(name, teamId);
|
|
17281
17637
|
if (free === false) {
|
|
17282
|
-
console.log(
|
|
17638
|
+
console.log(import_chalk52.default.yellow(` "${name}" is taken. Try another.
|
|
17283
17639
|
`));
|
|
17284
17640
|
continue;
|
|
17285
17641
|
}
|
|
17286
|
-
if (free === null) console.log(
|
|
17287
|
-
console.log(` ${
|
|
17642
|
+
if (free === null) console.log(import_chalk52.default.dim(" (could not verify availability; continuing)"));
|
|
17643
|
+
console.log(` ${import_chalk52.default.cyan("\u2192")} ${import_chalk52.default.bold(`https://${name}.abz.run`)}`);
|
|
17288
17644
|
return name;
|
|
17289
17645
|
}
|
|
17290
17646
|
}
|
|
@@ -17302,20 +17658,20 @@ function printSummary(recipe, project, tenant, reuseTenant = false) {
|
|
|
17302
17658
|
const keyTypes = recipe.auth?.key_types?.length ?? 0;
|
|
17303
17659
|
const questions = reuseTenant ? 0 : recipe.questions?.length ?? 0;
|
|
17304
17660
|
const row = (label3, value, note) => {
|
|
17305
|
-
console.log(` ${
|
|
17661
|
+
console.log(` ${import_chalk52.default.dim(label3.padEnd(21))}${value}${note ? import_chalk52.default.dim(` ${note}`) : ""}`);
|
|
17306
17662
|
};
|
|
17307
17663
|
console.log();
|
|
17308
|
-
console.log(
|
|
17664
|
+
console.log(import_chalk52.default.bold(" This will:"));
|
|
17309
17665
|
console.log();
|
|
17310
|
-
row("create a proxy",
|
|
17311
|
-
row("for its users",
|
|
17666
|
+
row("create a proxy", import_chalk52.default.bold(project), `at https://${project}.abz.run`);
|
|
17667
|
+
row("for its users", import_chalk52.default.bold(tenant), reuseTenant ? "(one you already have)" : "(a new tenant)");
|
|
17312
17668
|
if (hosts.length) row("sending requests to", hosts.join(", "));
|
|
17313
17669
|
row("and set up", describeCreates(keyTypes, routes, recipe.settings.mcp_enabled === true, false));
|
|
17314
17670
|
row("asking you for", questions ? `${questions} value${questions === 1 ? "" : "s"}` : "nothing");
|
|
17315
17671
|
if (reuseTenant) {
|
|
17316
17672
|
console.log();
|
|
17317
|
-
console.log(
|
|
17318
|
-
console.log(
|
|
17673
|
+
console.log(import_chalk52.default.dim(` ${tenant} is used exactly as it is \u2014 its existing login stays, and this`));
|
|
17674
|
+
console.log(import_chalk52.default.dim(" recipe's own login setup is not applied."));
|
|
17319
17675
|
}
|
|
17320
17676
|
console.log();
|
|
17321
17677
|
}
|
|
@@ -17324,11 +17680,11 @@ function declineAll(items) {
|
|
|
17324
17680
|
const upstreams = items.filter((i) => i.kind === "upstream");
|
|
17325
17681
|
console.log();
|
|
17326
17682
|
for (const u of upstreams) {
|
|
17327
|
-
console.log(
|
|
17683
|
+
console.log(import_chalk52.default.yellow(` --yes: this proxy sends every request to ${import_chalk52.default.bold(u.value)}.`));
|
|
17328
17684
|
}
|
|
17329
17685
|
if (skippable.length) {
|
|
17330
|
-
console.log(
|
|
17331
|
-
for (const i of skippable) console.log(
|
|
17686
|
+
console.log(import_chalk52.default.yellow(` --yes: installing without ${skippable.length} setting${skippable.length === 1 ? "" : "s"} that need consent.`));
|
|
17687
|
+
for (const i of skippable) console.log(import_chalk52.default.dim(` skipped ${i.kind} ${i.value}`));
|
|
17332
17688
|
}
|
|
17333
17689
|
return new Set(skippable.map(declineKey));
|
|
17334
17690
|
}
|
|
@@ -17349,14 +17705,14 @@ async function askConsent(items, interactive2, reinstalling) {
|
|
|
17349
17705
|
const count = items.length;
|
|
17350
17706
|
const word = ["", "it", "both", "all three", "all four", "all five", "all six"][count] ?? `all ${count}`;
|
|
17351
17707
|
console.log();
|
|
17352
|
-
console.log(
|
|
17708
|
+
console.log(import_chalk52.default.yellow(reinstalling ? "\u26A0 This recipe changes who can reach your proxy, what it trusts, and what it can cost." : "\u26A0 The proxy this creates will be reachable by others, trust an upstream host, and can cost you money:"));
|
|
17353
17709
|
console.log();
|
|
17354
17710
|
for (const item of items) {
|
|
17355
|
-
console.log(` ${
|
|
17356
|
-
for (const line of wrapText(item.why, 62)) console.log(` ${" ".repeat(16)} ${
|
|
17711
|
+
console.log(` ${import_chalk52.default.bold(item.kind.padEnd(16))} ${item.value}`);
|
|
17712
|
+
for (const line of wrapText(item.why, 62)) console.log(` ${" ".repeat(16)} ${import_chalk52.default.dim(line)}`);
|
|
17357
17713
|
}
|
|
17358
17714
|
console.log();
|
|
17359
|
-
console.log(`Type "${
|
|
17715
|
+
console.log(`Type "${import_chalk52.default.bold("yes")}" to accept ${word}.`);
|
|
17360
17716
|
console.log("Press enter to install the recipe without them.");
|
|
17361
17717
|
let typed = "";
|
|
17362
17718
|
if (interactive2) {
|
|
@@ -17368,7 +17724,7 @@ async function askConsent(items, interactive2, reinstalling) {
|
|
|
17368
17724
|
for (const item of items) {
|
|
17369
17725
|
declined.add(declineKey(item));
|
|
17370
17726
|
}
|
|
17371
|
-
console.log(
|
|
17727
|
+
console.log(import_chalk52.default.dim(` Installing without ${count} setting${count === 1 ? "" : "s"} \u2014 everything else is applied.`));
|
|
17372
17728
|
return declined;
|
|
17373
17729
|
}
|
|
17374
17730
|
async function askQuestions(questions, ctx, interactive2) {
|
|
@@ -17381,13 +17737,13 @@ async function askQuestions(questions, ctx, interactive2) {
|
|
|
17381
17737
|
const BOX = 64;
|
|
17382
17738
|
const title = "\u2500\u2500 from the publisher ";
|
|
17383
17739
|
console.log();
|
|
17384
|
-
console.log(` ${
|
|
17385
|
-
for (const line of fillContext(q.before, ctx).split("\n")) console.log(` ${
|
|
17386
|
-
console.log(` ${
|
|
17740
|
+
console.log(` ${import_chalk52.default.dim(title + "\u2500".repeat(BOX - title.length))}`);
|
|
17741
|
+
for (const line of fillContext(q.before, ctx).split("\n")) console.log(` ${import_chalk52.default.dim("\u2502")} ${line}`);
|
|
17742
|
+
console.log(` ${import_chalk52.default.dim("\u2500".repeat(BOX))}`);
|
|
17387
17743
|
}
|
|
17388
17744
|
if (fromEnv !== void 0 && fromEnv !== "") {
|
|
17389
17745
|
answers[q.id] = fromEnv;
|
|
17390
|
-
console.log(` ${q.prompt}: ${
|
|
17746
|
+
console.log(` ${q.prompt}: ${import_chalk52.default.dim(`(from ${envName})`)}`);
|
|
17391
17747
|
continue;
|
|
17392
17748
|
}
|
|
17393
17749
|
if (!interactive2) {
|
|
@@ -17397,7 +17753,7 @@ async function askQuestions(questions, ctx, interactive2) {
|
|
|
17397
17753
|
);
|
|
17398
17754
|
}
|
|
17399
17755
|
const { default: inquirer3 } = await import("inquirer");
|
|
17400
|
-
if (q.example && !q.secret) console.log(
|
|
17756
|
+
if (q.example && !q.secret) console.log(import_chalk52.default.dim(` e.g. ${q.example}`));
|
|
17401
17757
|
const { value } = await inquirer3.prompt([{
|
|
17402
17758
|
// `secret: true` masks the input — the Google client secret is typed once,
|
|
17403
17759
|
// goes to provider creation, and is never echoed or stored.
|
|
@@ -17431,30 +17787,30 @@ async function confirmReinstall(recipe, project, tenant, interactive2) {
|
|
|
17431
17787
|
const tables = recipe.transformations?.mapping_tables?.length ?? 0;
|
|
17432
17788
|
const routes = countRoutes(recipe.openapi);
|
|
17433
17789
|
const keyTypes = recipe.auth?.key_types?.length ?? 0;
|
|
17434
|
-
console.log(`This installs ${
|
|
17790
|
+
console.log(`This installs ${import_chalk52.default.bold(`${recipe.name}@${recipe.revision}`)} over ${import_chalk52.default.bold(project)}.`);
|
|
17435
17791
|
console.log();
|
|
17436
|
-
console.log(` ${
|
|
17792
|
+
console.log(` ${import_chalk52.default.dim("settings ".padEnd(16))}replaced`);
|
|
17437
17793
|
if (rules || tables) {
|
|
17438
|
-
console.log(` ${
|
|
17794
|
+
console.log(` ${import_chalk52.default.dim("transforms".padEnd(16))}${rules} rule${rules === 1 ? "" : "s"}${tables ? ` and ${tables} mapping table${tables === 1 ? "" : "s"}` : ""} replaced \u2014 your edits to them are lost`);
|
|
17439
17795
|
}
|
|
17440
17796
|
if (routes) {
|
|
17441
|
-
console.log(` ${
|
|
17797
|
+
console.log(` ${import_chalk52.default.dim("api spec ".padEnd(16))}replaced, ${routes} routes regenerated${recipe.settings.mcp_enabled === true ? ", MCP rebuilt" : ""}`);
|
|
17442
17798
|
}
|
|
17443
17799
|
if (keyTypes) {
|
|
17444
|
-
console.log(` ${
|
|
17800
|
+
console.log(` ${import_chalk52.default.dim("key types ".padEnd(16))}${keyTypes} added if missing; existing types left alone`);
|
|
17445
17801
|
}
|
|
17446
17802
|
console.log();
|
|
17447
|
-
console.log(` ${
|
|
17803
|
+
console.log(` ${import_chalk52.default.dim("unchanged ".padEnd(16))}your API keys, your proxy URL, your users,`);
|
|
17448
17804
|
console.log(` ${" ".repeat(16)}and any tenant other than ${tenant}`);
|
|
17449
17805
|
console.log();
|
|
17450
|
-
console.log(
|
|
17806
|
+
console.log(import_chalk52.default.yellow("This cannot be undone."));
|
|
17451
17807
|
if (!interactive2) {
|
|
17452
17808
|
throw new Error("Reinstall needs a typed confirmation and there is no terminal to ask on.");
|
|
17453
17809
|
}
|
|
17454
17810
|
const { default: inquirer3 } = await import("inquirer");
|
|
17455
17811
|
const { typed } = await inquirer3.prompt([{ type: "input", name: "typed", message: `Type the proxy name (${project}) to continue:` }]);
|
|
17456
17812
|
if (String(typed ?? "").trim() !== project) {
|
|
17457
|
-
console.log(
|
|
17813
|
+
console.log(import_chalk52.default.yellow("Cancelled \u2014 nothing was changed."));
|
|
17458
17814
|
process.exit(0);
|
|
17459
17815
|
}
|
|
17460
17816
|
}
|
|
@@ -17462,7 +17818,7 @@ function requireCallerForInstall() {
|
|
|
17462
17818
|
try {
|
|
17463
17819
|
return requireCaller();
|
|
17464
17820
|
} catch {
|
|
17465
|
-
console.error(
|
|
17821
|
+
console.error(import_chalk52.default.red(
|
|
17466
17822
|
"Installing writes a proxy into an account, so it needs a credential.\n Run `apiblaze login`, or `apiblaze create` first (an anonymous workspace works too)."
|
|
17467
17823
|
));
|
|
17468
17824
|
process.exit(1);
|
|
@@ -17498,7 +17854,7 @@ async function chooseTeam(caller, optTeam, interactive2) {
|
|
|
17498
17854
|
message: " which team should own this proxy?",
|
|
17499
17855
|
default: active.teamId,
|
|
17500
17856
|
choices: teams.map((t) => ({
|
|
17501
|
-
name: t.teamId === active.teamId ? `${t.name} ${
|
|
17857
|
+
name: t.teamId === active.teamId ? `${t.name} ${import_chalk52.default.dim("(active)")}` : t.name,
|
|
17502
17858
|
value: t.teamId
|
|
17503
17859
|
}))
|
|
17504
17860
|
}]);
|
|
@@ -17526,10 +17882,10 @@ async function chooseTenant(caller, project, teamId, optTenant, interactive2) {
|
|
|
17526
17882
|
message: " where should this proxy's users live?",
|
|
17527
17883
|
default: "__new__",
|
|
17528
17884
|
choices: [
|
|
17529
|
-
{ name: `create ${fresh} ${
|
|
17530
|
-
new inquirer3.Separator(
|
|
17885
|
+
{ name: `create ${fresh} ${import_chalk52.default.dim("(a fresh tenant, set up by the recipe)")}`, value: "__new__" },
|
|
17886
|
+
new inquirer3.Separator(import_chalk52.default.dim(" \u2014 or reuse one you already have \u2014")),
|
|
17531
17887
|
...owned.map((t2) => ({
|
|
17532
|
-
name: `${t2} ${
|
|
17888
|
+
name: `${t2} ${import_chalk52.default.dim("(existing \u2014 its own login is kept)")}`,
|
|
17533
17889
|
value: t2
|
|
17534
17890
|
}))
|
|
17535
17891
|
]
|
|
@@ -17559,8 +17915,8 @@ async function teamTenants(caller, teamId) {
|
|
|
17559
17915
|
}
|
|
17560
17916
|
|
|
17561
17917
|
// src/commands/recipe-publish.ts
|
|
17562
|
-
var
|
|
17563
|
-
var
|
|
17918
|
+
var import_chalk53 = __toESM(require("chalk"));
|
|
17919
|
+
var import_ora19 = __toESM(require("ora"));
|
|
17564
17920
|
init_caller();
|
|
17565
17921
|
init_auth();
|
|
17566
17922
|
async function runRecipePublish(projectArg, opts) {
|
|
@@ -17569,22 +17925,22 @@ async function runRecipePublish(projectArg, opts) {
|
|
|
17569
17925
|
try {
|
|
17570
17926
|
resolved = resolveRecipeName(opts.as, creds?.githubHandle, normalizeName3(projectArg));
|
|
17571
17927
|
} catch (err) {
|
|
17572
|
-
console.error(
|
|
17928
|
+
console.error(import_chalk53.default.red(err instanceof Error ? err.message : String(err)));
|
|
17573
17929
|
process.exitCode = 1;
|
|
17574
17930
|
return;
|
|
17575
17931
|
}
|
|
17576
17932
|
const ref = resolved.ref;
|
|
17577
17933
|
if (resolved.derivedHandle) {
|
|
17578
|
-
console.log(
|
|
17934
|
+
console.log(import_chalk53.default.dim(` publishing as ${import_chalk53.default.bold(ref.name)}`));
|
|
17579
17935
|
if (resolved.normalizedFrom) {
|
|
17580
|
-
console.log(
|
|
17936
|
+
console.log(import_chalk53.default.dim(` (your GitHub login is ${resolved.normalizedFrom}; apiblaze names are letters and digits only)`));
|
|
17581
17937
|
}
|
|
17582
17938
|
}
|
|
17583
17939
|
const interactive2 = !!process.stdin.isTTY && !opts.json;
|
|
17584
17940
|
const caller = requireCaller();
|
|
17585
17941
|
const { teamId } = await resolveActingTeam(caller, opts.team);
|
|
17586
17942
|
const proj2 = await resolveProjectVia(caller, teamId, projectArg, opts.apiversion);
|
|
17587
|
-
const spinner = !opts.json ? (0,
|
|
17943
|
+
const spinner = !opts.json ? (0, import_ora19.default)(`Reading ${proj2.projectId} v${proj2.apiVersion}...`).start() : null;
|
|
17588
17944
|
let prep;
|
|
17589
17945
|
try {
|
|
17590
17946
|
prep = await prepare(caller, proj2.projectId, proj2.apiVersion, ref.name, opts.tenant);
|
|
@@ -17596,7 +17952,7 @@ async function runRecipePublish(projectArg, opts) {
|
|
|
17596
17952
|
let tenant = opts.tenant;
|
|
17597
17953
|
if (!prep.tenant_used && prep.tenants.length > 1) {
|
|
17598
17954
|
if (!interactive2) {
|
|
17599
|
-
console.error(
|
|
17955
|
+
console.error(import_chalk53.default.red(
|
|
17600
17956
|
`${proj2.projectId} has ${prep.tenants.length} tenants (${prep.tenants.join(", ")}). Pass --tenant <slug> to say which auth setup the recipe carries.`
|
|
17601
17957
|
));
|
|
17602
17958
|
process.exit(1);
|
|
@@ -17612,7 +17968,7 @@ async function runRecipePublish(projectArg, opts) {
|
|
|
17612
17968
|
prep = await prepare(caller, proj2.projectId, proj2.apiVersion, ref.name, tenant);
|
|
17613
17969
|
}
|
|
17614
17970
|
if (!prep.name_available) {
|
|
17615
|
-
console.error(
|
|
17971
|
+
console.error(import_chalk53.default.red(`${ref.name} belongs to another team, so you cannot publish a revision of it.`));
|
|
17616
17972
|
process.exit(1);
|
|
17617
17973
|
}
|
|
17618
17974
|
if (prep.envelope_violations.length) {
|
|
@@ -17622,13 +17978,13 @@ async function runRecipePublish(projectArg, opts) {
|
|
|
17622
17978
|
if (prep.dropped.length) {
|
|
17623
17979
|
console.log();
|
|
17624
17980
|
console.log(`${prep.dropped.length} setting${prep.dropped.length === 1 ? " was" : "s were"} not included, because they are not on the publish list:`);
|
|
17625
|
-
for (const d of prep.dropped) console.log(
|
|
17981
|
+
for (const d of prep.dropped) console.log(import_chalk53.default.dim(` ${d}`));
|
|
17626
17982
|
if (prep.hidden_tables_excluded) {
|
|
17627
|
-
console.log(
|
|
17983
|
+
console.log(import_chalk53.default.dim(` (${prep.hidden_tables_excluded} hidden mapping table${prep.hidden_tables_excluded === 1 ? "" : "s"} excluded \u2014 those values belong to your consumers.)`));
|
|
17628
17984
|
}
|
|
17629
17985
|
console.log();
|
|
17630
17986
|
if (!opts.yes && !await confirm2(" continue without them?", interactive2)) {
|
|
17631
|
-
console.log(
|
|
17987
|
+
console.log(import_chalk53.default.yellow("Cancelled."));
|
|
17632
17988
|
return;
|
|
17633
17989
|
}
|
|
17634
17990
|
}
|
|
@@ -17641,12 +17997,12 @@ async function runRecipePublish(projectArg, opts) {
|
|
|
17641
17997
|
console.log();
|
|
17642
17998
|
for (const f2 of prep.findings) printFinding(f2);
|
|
17643
17999
|
console.log();
|
|
17644
|
-
console.error(
|
|
18000
|
+
console.error(import_chalk53.default.red("--yes cannot accept scanner findings. Fix the value in the proxy, or publish interactively and decide."));
|
|
17645
18001
|
process.exit(1);
|
|
17646
18002
|
}
|
|
17647
18003
|
if (!interactive2) {
|
|
17648
18004
|
for (const f2 of prep.findings) printFinding(f2);
|
|
17649
|
-
console.error(
|
|
18005
|
+
console.error(import_chalk53.default.red("\nThese need a decision and there is no terminal to ask on."));
|
|
17650
18006
|
process.exit(1);
|
|
17651
18007
|
}
|
|
17652
18008
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -17667,10 +18023,10 @@ async function runRecipePublish(projectArg, opts) {
|
|
|
17667
18023
|
if (verdict === "ask") promotions.push(await askPromotion(f2.path));
|
|
17668
18024
|
}
|
|
17669
18025
|
}
|
|
17670
|
-
console.log(
|
|
18026
|
+
console.log(import_chalk53.default.dim(`we cannot judge the other ${prep.unjudged_count} values. run \`apiblaze show ${ref.name}\` after publishing to read the whole recipe.`));
|
|
17671
18027
|
const totalQuestions = prep.question_ids.length + promotions.length;
|
|
17672
18028
|
if (totalQuestions > 5) {
|
|
17673
|
-
console.log(
|
|
18029
|
+
console.log(import_chalk53.default.yellow(`
|
|
17674
18030
|
\u26A0 installers will have to supply ${totalQuestions} values before this works.`));
|
|
17675
18031
|
}
|
|
17676
18032
|
const visibility = opts.private ? "private" : "public";
|
|
@@ -17678,15 +18034,15 @@ async function runRecipePublish(projectArg, opts) {
|
|
|
17678
18034
|
if (visibility === "public" && prep.spec_will_become_public) {
|
|
17679
18035
|
console.log();
|
|
17680
18036
|
console.log("Publishing publicly also makes this API's spec readable at");
|
|
17681
|
-
console.log(` ${
|
|
18037
|
+
console.log(` ${import_chalk53.default.bold(`https://${proj2.projectId}.abz.run/${proj2.apiVersion}/openapi.json`)}`);
|
|
17682
18038
|
console.log("It is currently private.");
|
|
17683
18039
|
confirmSpecPublic = opts.yes ? true : await confirm2("Continue?", interactive2);
|
|
17684
18040
|
if (!confirmSpecPublic) {
|
|
17685
|
-
console.log(
|
|
18041
|
+
console.log(import_chalk53.default.yellow("Cancelled \u2014 nothing was published."));
|
|
17686
18042
|
return;
|
|
17687
18043
|
}
|
|
17688
18044
|
}
|
|
17689
|
-
const spin2 = !opts.json ? (0,
|
|
18045
|
+
const spin2 = !opts.json ? (0, import_ora19.default)(`Publishing ${ref.name}...`).start() : null;
|
|
17690
18046
|
let out;
|
|
17691
18047
|
try {
|
|
17692
18048
|
out = await producer(caller, {
|
|
@@ -17708,7 +18064,7 @@ async function runRecipePublish(projectArg, opts) {
|
|
|
17708
18064
|
},
|
|
17709
18065
|
summary: `Publish ${proj2.projectId} as ${ref.name} (${visibility})`
|
|
17710
18066
|
});
|
|
17711
|
-
spin2?.succeed(`Published ${
|
|
18067
|
+
spin2?.succeed(`Published ${import_chalk53.default.bold(`${out.name}@${out.revision}`)} (${out.visibility}).`);
|
|
17712
18068
|
} catch (err) {
|
|
17713
18069
|
spin2?.fail("Publish failed.");
|
|
17714
18070
|
throw err;
|
|
@@ -17717,15 +18073,15 @@ async function runRecipePublish(projectArg, opts) {
|
|
|
17717
18073
|
console.log(JSON.stringify(out));
|
|
17718
18074
|
return;
|
|
17719
18075
|
}
|
|
17720
|
-
console.log(` ${
|
|
17721
|
-
console.log(` ${
|
|
18076
|
+
console.log(` ${import_chalk53.default.dim("size ")} ${(out.size_bytes / 1024).toFixed(1)} KB`);
|
|
18077
|
+
console.log(` ${import_chalk53.default.dim("checksum")} ${out.sha256.slice(0, 16)}\u2026`);
|
|
17722
18078
|
console.log();
|
|
17723
18079
|
if (out.visibility === "public") {
|
|
17724
|
-
console.log(
|
|
18080
|
+
console.log(import_chalk53.default.dim(` Anyone can now run: ${import_chalk53.default.cyan(`npx apiblaze install ${out.name}`)}`));
|
|
17725
18081
|
} else {
|
|
17726
|
-
console.log(
|
|
18082
|
+
console.log(import_chalk53.default.dim(" Private \u2014 only members of this team can see or install it."));
|
|
17727
18083
|
}
|
|
17728
|
-
console.log(
|
|
18084
|
+
console.log(import_chalk53.default.dim(` Pull it back with: ${import_chalk53.default.cyan(`npx apiblaze withdraw ${out.name}@${out.revision}`)}`));
|
|
17729
18085
|
console.log();
|
|
17730
18086
|
}
|
|
17731
18087
|
async function prepare(caller, projectId, apiVersion, name, tenant) {
|
|
@@ -17738,23 +18094,23 @@ async function prepare(caller, projectId, apiVersion, name, tenant) {
|
|
|
17738
18094
|
}
|
|
17739
18095
|
function printFinding(f2) {
|
|
17740
18096
|
const preview = (f2.preview ?? "").replace(/\s+/g, " ").trim();
|
|
17741
|
-
console.log(` ${
|
|
17742
|
-
console.log(` ${
|
|
18097
|
+
console.log(` ${import_chalk53.default.bold(f2.path)}`);
|
|
18098
|
+
console.log(` ${import_chalk53.default.yellow(preview.padEnd(38))}${import_chalk53.default.dim(f2.why)}`);
|
|
17743
18099
|
}
|
|
17744
18100
|
function printEnvelopeViolations(violations) {
|
|
17745
18101
|
console.log();
|
|
17746
|
-
console.error(
|
|
18102
|
+
console.error(import_chalk53.default.red("This proxy has transform rules a recipe cannot carry."));
|
|
17747
18103
|
console.log();
|
|
17748
18104
|
for (const v of violations) {
|
|
17749
|
-
console.log(` ${
|
|
18105
|
+
console.log(` ${import_chalk53.default.bold(v.rule)} ${import_chalk53.default.dim(`(${v.kind}s ${v.field})`)}`);
|
|
17750
18106
|
console.log(` ${v.why}`);
|
|
17751
18107
|
}
|
|
17752
18108
|
console.log();
|
|
17753
18109
|
console.log("A recipe runs in a stranger's account, after apiblaze has attached their end user's");
|
|
17754
18110
|
console.log("real third-party token, so two rules are absolute:");
|
|
17755
|
-
console.log(` ${
|
|
18111
|
+
console.log(` ${import_chalk53.default.dim("\u2022")} a rule may never READ Authorization, Cookie, or the third-party token headers \u2014`);
|
|
17756
18112
|
console.log(" those hold a live credential that works at the provider without apiblaze;");
|
|
17757
|
-
console.log(` ${
|
|
18113
|
+
console.log(` ${import_chalk53.default.dim("\u2022")} a rule may never WRITE an identity header (x-end-user-id and friends) \u2014`);
|
|
17758
18114
|
console.log(" those tell the installer's own backend who is calling.");
|
|
17759
18115
|
console.log();
|
|
17760
18116
|
console.log("Writing Authorization from a question or a fixed value is fine \u2014 that is the");
|
|
@@ -17772,7 +18128,7 @@ async function askPromotion(path20) {
|
|
|
17772
18128
|
{ type: "input", name: "id", message: " id:", default: suggestedId }
|
|
17773
18129
|
]);
|
|
17774
18130
|
const id = String(answers.id || suggestedId).toLowerCase().replace(/[^a-z0-9_]/g, "_");
|
|
17775
|
-
console.log(
|
|
18131
|
+
console.log(import_chalk53.default.dim(` \u2192 replaced with {{${id}}}`));
|
|
17776
18132
|
return {
|
|
17777
18133
|
path: path20,
|
|
17778
18134
|
id,
|
|
@@ -17791,7 +18147,7 @@ async function confirm2(message, interactive2) {
|
|
|
17791
18147
|
async function runRecipeWithdraw(nameArg, opts) {
|
|
17792
18148
|
const ref = parseRecipeRef(nameArg);
|
|
17793
18149
|
if (ref.revision === void 0) {
|
|
17794
|
-
console.error(
|
|
18150
|
+
console.error(import_chalk53.default.red(`Name the revision to withdraw, e.g. ${nameArg}@3. Revisions are withdrawn one at a time.`));
|
|
17795
18151
|
process.exit(1);
|
|
17796
18152
|
}
|
|
17797
18153
|
const interactive2 = !!process.stdin.isTTY && !opts.json;
|
|
@@ -17811,14 +18167,14 @@ async function runRecipeWithdraw(nameArg, opts) {
|
|
|
17811
18167
|
const { default: inquirer3 } = await import("inquirer");
|
|
17812
18168
|
const { typed } = await inquirer3.prompt([{ type: "input", name: "typed", message: `type the recipe name to confirm (${ref.name}):` }]);
|
|
17813
18169
|
if (String(typed ?? "").trim() !== ref.name) {
|
|
17814
|
-
console.log(
|
|
18170
|
+
console.log(import_chalk53.default.yellow("Cancelled \u2014 nothing was withdrawn."));
|
|
17815
18171
|
return;
|
|
17816
18172
|
}
|
|
17817
18173
|
} else {
|
|
17818
|
-
console.error(
|
|
18174
|
+
console.error(import_chalk53.default.red("Withdrawing needs a typed confirmation and there is no terminal to ask on."));
|
|
17819
18175
|
process.exit(1);
|
|
17820
18176
|
}
|
|
17821
|
-
const spinner = !opts.json ? (0,
|
|
18177
|
+
const spinner = !opts.json ? (0, import_ora19.default)("Withdrawing...").start() : null;
|
|
17822
18178
|
let out;
|
|
17823
18179
|
try {
|
|
17824
18180
|
out = await producer(caller, {
|
|
@@ -17847,7 +18203,7 @@ async function runRecipeWithdraw(nameArg, opts) {
|
|
|
17847
18203
|
init_logs();
|
|
17848
18204
|
|
|
17849
18205
|
// src/commands/consumer-logs.ts
|
|
17850
|
-
var
|
|
18206
|
+
var import_chalk54 = __toESM(require("chalk"));
|
|
17851
18207
|
init_auth();
|
|
17852
18208
|
init_consumer_auth();
|
|
17853
18209
|
init_log_view();
|
|
@@ -17855,9 +18211,9 @@ var CONSUMER_LOGS_BASE = process.env.APIBLAZE_CONSUMER_LOGS_BASE || "https://log
|
|
|
17855
18211
|
function requireConsumer() {
|
|
17856
18212
|
const c = loadConsumer();
|
|
17857
18213
|
if (!c) {
|
|
17858
|
-
console.error(
|
|
18214
|
+
console.error(import_chalk54.default.red("Not logged in as an API Consumer. Run `apiblaze consumer login` first."));
|
|
17859
18215
|
if (loadCredentials()) {
|
|
17860
|
-
console.error(
|
|
18216
|
+
console.error(import_chalk54.default.dim("You're signed in as an API Producer \u2014 for a proxy's full logs run `apiblaze logs <project>`."));
|
|
17861
18217
|
}
|
|
17862
18218
|
process.exit(1);
|
|
17863
18219
|
}
|
|
@@ -17888,7 +18244,7 @@ async function consumerLogsFetch(session, suffix, init) {
|
|
|
17888
18244
|
return data;
|
|
17889
18245
|
}
|
|
17890
18246
|
function otherPlaneHint2() {
|
|
17891
|
-
if (loadCredentials()) console.log(
|
|
18247
|
+
if (loadCredentials()) console.log(import_chalk54.default.dim(" \u2039producer view: apiblaze logs <project>\u203A"));
|
|
17892
18248
|
}
|
|
17893
18249
|
function fetchWindow2(session, project) {
|
|
17894
18250
|
return (params) => {
|
|
@@ -17904,8 +18260,8 @@ function fetchWindow2(session, project) {
|
|
|
17904
18260
|
async function runConsumerLogsTail(opts) {
|
|
17905
18261
|
const session = requireConsumer();
|
|
17906
18262
|
if (!opts.json) {
|
|
17907
|
-
console.log(
|
|
17908
|
-
console.log(
|
|
18263
|
+
console.log(import_chalk54.default.bold("API Consumer logs") + import_chalk54.default.dim(` \xB7 your own requests on ${session.creds.tenant}${opts.project ? ` \xB7 ${opts.project}` : ""}${opts.errorsOnly ? " \xB7 errors only" : ""}`));
|
|
18264
|
+
console.log(import_chalk54.default.dim("cost and upstream details are producer-only and are not sent"));
|
|
17909
18265
|
otherPlaneHint2();
|
|
17910
18266
|
}
|
|
17911
18267
|
await runTailSession({
|
|
@@ -17923,8 +18279,8 @@ async function runConsumerLogsTail(opts) {
|
|
|
17923
18279
|
async function runConsumerLogsList(opts) {
|
|
17924
18280
|
const session = requireConsumer();
|
|
17925
18281
|
if (!opts.json) {
|
|
17926
|
-
console.log(
|
|
17927
|
-
console.log(
|
|
18282
|
+
console.log(import_chalk54.default.bold("API Consumer logs") + import_chalk54.default.dim(` \xB7 your own requests on ${session.creds.tenant}${opts.project ? ` \xB7 ${opts.project}` : ""}${opts.errorsOnly ? " \xB7 errors only" : ""}`));
|
|
18283
|
+
console.log(import_chalk54.default.dim("cost and upstream details are producer-only and are not sent"));
|
|
17928
18284
|
otherPlaneHint2();
|
|
17929
18285
|
console.log("");
|
|
17930
18286
|
}
|
|
@@ -17937,71 +18293,12 @@ async function runConsumerLogsList(opts) {
|
|
|
17937
18293
|
});
|
|
17938
18294
|
}
|
|
17939
18295
|
|
|
17940
|
-
// src/
|
|
17941
|
-
|
|
17942
|
-
var import_ora18 = __toESM(require("ora"));
|
|
17943
|
-
init_caller();
|
|
17944
|
-
init_paging();
|
|
17945
|
-
async function patchConfig(project, opts, body, summary) {
|
|
17946
|
-
const caller = requireCaller();
|
|
17947
|
-
const { teamId } = await resolveActingTeam(caller, opts.team);
|
|
17948
|
-
const proj2 = await resolveProjectVia(caller, teamId, project, opts.apiversion);
|
|
17949
|
-
const spinner = (0, import_ora18.default)(summary + "...").start();
|
|
17950
|
-
try {
|
|
17951
|
-
const out = await producer(caller, {
|
|
17952
|
-
method: "PATCH",
|
|
17953
|
-
path: `/projects/${proj2.projectId}/${proj2.apiVersion}`,
|
|
17954
|
-
body,
|
|
17955
|
-
summary
|
|
17956
|
-
});
|
|
17957
|
-
spinner.succeed(`${summary} \u2014 ${proj2.projectName} v${proj2.apiVersion}.`);
|
|
17958
|
-
if (opts.json) console.log(JSON.stringify(out ?? { ok: true }));
|
|
17959
|
-
} catch (err) {
|
|
17960
|
-
spinner.fail(summary + " failed.");
|
|
17961
|
-
throw err;
|
|
17962
|
-
}
|
|
17963
|
-
}
|
|
17964
|
-
async function runTargetSet(project, opts) {
|
|
17965
|
-
if (!opts.url) {
|
|
17966
|
-
console.error(import_chalk53.default.red("--url is required."));
|
|
17967
|
-
process.exit(1);
|
|
17968
|
-
}
|
|
17969
|
-
if (!opts.env) {
|
|
17970
|
-
await patchConfig(project, opts, { target_url: opts.url }, `Set target \u2192 ${opts.url}`);
|
|
17971
|
-
return;
|
|
17972
|
-
}
|
|
17973
|
-
const caller = requireCaller();
|
|
17974
|
-
const { teamId } = await resolveActingTeam(caller, opts.team);
|
|
17975
|
-
const proj2 = await resolveProjectVia(caller, teamId, project, opts.apiversion);
|
|
17976
|
-
const listing = await pageAll(
|
|
17977
|
-
(cursor) => producer(caller, {
|
|
17978
|
-
method: "GET",
|
|
17979
|
-
path: withPage(`/projects?team_id=${encodeURIComponent(teamId)}`, cursor),
|
|
17980
|
-
summary: `Read environments for ${proj2.projectName}`
|
|
17981
|
-
}),
|
|
17982
|
-
(b) => b?.projects
|
|
17983
|
-
);
|
|
17984
|
-
const row = listing.items.find((r) => r.project_id === proj2.projectId && r.api_version === proj2.apiVersion);
|
|
17985
|
-
const current = row?.config?.environments;
|
|
17986
|
-
const existing = current && typeof current === "object" && !Array.isArray(current) ? current : {};
|
|
17987
|
-
const prev = existing[opts.env];
|
|
17988
|
-
const environments = {
|
|
17989
|
-
...existing,
|
|
17990
|
-
[opts.env]: { ...prev && typeof prev === "object" ? prev : {}, target: opts.url }
|
|
17991
|
-
};
|
|
17992
|
-
await patchConfig(project, opts, { environments }, `Set target for env ${opts.env} \u2192 ${opts.url}`);
|
|
17993
|
-
}
|
|
17994
|
-
async function runRename(project, opts) {
|
|
17995
|
-
if (!opts.displayName) {
|
|
17996
|
-
console.error(import_chalk53.default.red("--display-name is required."));
|
|
17997
|
-
process.exit(1);
|
|
17998
|
-
}
|
|
17999
|
-
await patchConfig(project, opts, { display_name: opts.displayName }, `Rename \u2192 "${opts.displayName}"`);
|
|
18000
|
-
}
|
|
18296
|
+
// src/cli.ts
|
|
18297
|
+
init_config();
|
|
18001
18298
|
|
|
18002
18299
|
// src/commands/limits.ts
|
|
18003
|
-
var
|
|
18004
|
-
var
|
|
18300
|
+
var import_chalk55 = __toESM(require("chalk"));
|
|
18301
|
+
var import_ora20 = __toESM(require("ora"));
|
|
18005
18302
|
init_caller();
|
|
18006
18303
|
init_admin();
|
|
18007
18304
|
init_resolve();
|
|
@@ -18099,18 +18396,18 @@ function renderLimits(r, name) {
|
|
|
18099
18396
|
const cred = l.credentialQuota ? `${l.credentialQuota.limit.toLocaleString()} per key (${l.credentialQuota.period})` : "(none)";
|
|
18100
18397
|
const cap2 = typeof l.spendCapUnits === "number" ? usd(Math.round(l.spendCapUnits / LEDGER_UNITS_PER_CENT)) + " / month" : "(none)";
|
|
18101
18398
|
const lines = [
|
|
18102
|
-
`${
|
|
18399
|
+
`${import_chalk55.default.bold(name)}${r.api_version ? import_chalk55.default.dim(` v${r.api_version}`) : ""}${r.anonymous ? import_chalk55.default.dim(" (anonymous workspace)") : ""}`,
|
|
18103
18400
|
// Standard is the only mode; a stored `strict` (pre-2026-09-29 config) is
|
|
18104
18401
|
// served as standard by the API, and named as such here.
|
|
18105
|
-
` ${"mode".padEnd(18)} standard${l.mode === "strict" ? " (legacy strict setting)" : ""}${
|
|
18106
|
-
` ${"rate limit".padEnd(18)} ${ladder(l.consumerRps, t.consumerRps)}${
|
|
18107
|
-
` ${"end-user rate".padEnd(18)} ${ladder(l.endUserRps, t.endUserRps)}${
|
|
18108
|
-
` ${"ceiling".padEnd(18)} ${ladder(l.ceilingRps, t.ceilingRps)}${
|
|
18109
|
-
` ${"quota".padEnd(18)} ${quota}${
|
|
18402
|
+
` ${"mode".padEnd(18)} standard${l.mode === "strict" ? " (legacy strict setting)" : ""}${import_chalk55.default.dim(" per consumer key, approximate per location")}`,
|
|
18403
|
+
` ${"rate limit".padEnd(18)} ${ladder(l.consumerRps, t.consumerRps)}${import_chalk55.default.dim(" per consumer key")}`,
|
|
18404
|
+
` ${"end-user rate".padEnd(18)} ${ladder(l.endUserRps, t.endUserRps)}${import_chalk55.default.dim(" per X-End-User-Id")}`,
|
|
18405
|
+
` ${"ceiling".padEnd(18)} ${ladder(l.ceilingRps, t.ceilingRps)}${import_chalk55.default.dim(` whole proxy \xB7 read-only${typeof r.self_serve_ceiling_rps === "number" ? `, self-serve up to ${r.self_serve_ceiling_rps}/s` : ""}`)}`,
|
|
18406
|
+
` ${"quota".padEnd(18)} ${quota}${import_chalk55.default.dim(" fixed UTC windows")}`,
|
|
18110
18407
|
` ${"spend cap".padEnd(18)} ${cap2}`,
|
|
18111
18408
|
` ${"credential quota".padEnd(18)} ${cred}`
|
|
18112
18409
|
];
|
|
18113
|
-
if (typeof l.configVersion === "number") lines.push(
|
|
18410
|
+
if (typeof l.configVersion === "number") lines.push(import_chalk55.default.dim(` config version ${l.configVersion} \xB7 edits bind within 30 s, no reset on edit`));
|
|
18114
18411
|
return lines;
|
|
18115
18412
|
}
|
|
18116
18413
|
async function resolve2(project, opts) {
|
|
@@ -18129,7 +18426,7 @@ async function runLimitsGet(project, opts) {
|
|
|
18129
18426
|
if (opts.json) return void console.log(JSON.stringify(r, null, 2));
|
|
18130
18427
|
console.log("");
|
|
18131
18428
|
for (const l of renderLimits(r, proj2.projectName)) console.log(l);
|
|
18132
|
-
console.log(
|
|
18429
|
+
console.log(import_chalk55.default.dim(`
|
|
18133
18430
|
Change: ${B.cli} limits set ${proj2.projectName} --rate <n> [--end-user-rate <n>] [--quota <n> --period daily|weekly|monthly] [--spend-cap <usd>]
|
|
18134
18431
|
`));
|
|
18135
18432
|
}
|
|
@@ -18139,7 +18436,7 @@ async function runLimitsSet(project, opts) {
|
|
|
18139
18436
|
const needsCurrent = opts.quota !== void 0 && opts.period === void 0 || opts.period !== void 0 && opts.quota === void 0 || opts.credentialQuota !== void 0;
|
|
18140
18437
|
const current = needsCurrent ? (await getLimits(caller, proj2).catch(() => null))?.limits ?? null : null;
|
|
18141
18438
|
const body = buildLimitsPatch(opts, current);
|
|
18142
|
-
const spinner = (0,
|
|
18439
|
+
const spinner = (0, import_ora20.default)(`Update limits for ${proj2.projectName}...`).start();
|
|
18143
18440
|
let r;
|
|
18144
18441
|
try {
|
|
18145
18442
|
r = await producer(caller, { method: "PUT", path: limitsPath(proj2), body, summary: `Set limits ${JSON.stringify(body)}` });
|
|
@@ -18155,9 +18452,9 @@ async function runLimitsSet(project, opts) {
|
|
|
18155
18452
|
console.log(JSON.stringify({ error: inner.error ?? b.error, details: inner.details ?? b.details, max_self_serve_rps: inner.max_self_serve_rps ?? b.max_self_serve_rps, request_ceiling: hint, command: cmd }));
|
|
18156
18453
|
process.exit(1);
|
|
18157
18454
|
}
|
|
18158
|
-
console.log(
|
|
18159
|
-
if (typeof (inner.max_self_serve_rps ?? b.max_self_serve_rps) === "number") console.log(
|
|
18160
|
-
console.log(` Request it: ${
|
|
18455
|
+
console.log(import_chalk55.default.yellow(` ${inner.details ?? b.details ?? inner.error ?? b.error ?? "That ceiling needs approval."}`));
|
|
18456
|
+
if (typeof (inner.max_self_serve_rps ?? b.max_self_serve_rps) === "number") console.log(import_chalk55.default.dim(` Self-serve ceiling: ${inner.max_self_serve_rps ?? b.max_self_serve_rps}/s. Above that an operator approves.`));
|
|
18457
|
+
console.log(` Request it: ${import_chalk55.default.cyan(cmd)}`);
|
|
18161
18458
|
process.exit(1);
|
|
18162
18459
|
}
|
|
18163
18460
|
}
|
|
@@ -18167,7 +18464,7 @@ async function runLimitsSet(project, opts) {
|
|
|
18167
18464
|
if (opts.json) return void console.log(JSON.stringify(r, null, 2));
|
|
18168
18465
|
for (const l of renderLimits(r, proj2.projectName)) console.log(l);
|
|
18169
18466
|
if (resolveCaller()?.mode === "anon") {
|
|
18170
|
-
console.log(
|
|
18467
|
+
console.log(import_chalk55.default.dim(" Anonymous workspace: rates are capped at the anonymous ceiling until you claim it (`apiblaze login`, then `apiblaze claim`)."));
|
|
18171
18468
|
}
|
|
18172
18469
|
console.log("");
|
|
18173
18470
|
}
|
|
@@ -18176,7 +18473,7 @@ async function runLimitsRequestMax(project, opts) {
|
|
|
18176
18473
|
if (rps === void 0) throw new UsageError("--rps <n> is required: the ceiling you are asking for, in requests per second.");
|
|
18177
18474
|
const { caller, proj: proj2 } = await resolve2(project, opts);
|
|
18178
18475
|
const body = { requested_rps: rps, ...opts.useCase ? { use_case: opts.useCase } : {} };
|
|
18179
|
-
const spinner = (0,
|
|
18476
|
+
const spinner = (0, import_ora20.default)(`Request a ${rps}/s ceiling for ${proj2.projectName}...`).start();
|
|
18180
18477
|
let r;
|
|
18181
18478
|
try {
|
|
18182
18479
|
r = await producer(caller, { method: "POST", path: `${limitsPath(proj2)}/ceiling-request`, body, summary: `Request ceiling ${rps}/s` });
|
|
@@ -18186,8 +18483,8 @@ async function runLimitsRequestMax(project, opts) {
|
|
|
18186
18483
|
}
|
|
18187
18484
|
spinner.succeed(`Requested: ${ladder(rps)} for ${proj2.projectName}.`);
|
|
18188
18485
|
if (opts.json) return void console.log(JSON.stringify(r ?? { accepted: true, requested_rps: rps }, null, 2));
|
|
18189
|
-
console.log(
|
|
18190
|
-
console.log(
|
|
18486
|
+
console.log(import_chalk55.default.dim(" Up to 1,000/s is granted instantly to a proven wallet; above that an operator approves (1k / 10k / 100k presets)."));
|
|
18487
|
+
console.log(import_chalk55.default.dim(` Check: ${B.cli} limits get ${proj2.projectName}
|
|
18191
18488
|
`));
|
|
18192
18489
|
}
|
|
18193
18490
|
function renderTeamQuota(q, teamLabel2, now = Date.now()) {
|
|
@@ -18202,9 +18499,9 @@ function renderTeamQuota(q, teamLabel2, now = Date.now()) {
|
|
|
18202
18499
|
resets = Number.isFinite(ms2) && ms2 > now ? `${q.resets_at.replace("T", " ").slice(0, 16)} UTC (in ${humanIn(ms2 - now)})` : q.resets_at;
|
|
18203
18500
|
}
|
|
18204
18501
|
return [
|
|
18205
|
-
`${
|
|
18502
|
+
`${import_chalk55.default.bold(teamLabel2)} \u2014 team quota${q.quotaPeriod ? ` (${q.quotaPeriod})` : ""}`,
|
|
18206
18503
|
` ${"used".padEnd(12)} ${used.toLocaleString()}`,
|
|
18207
|
-
` ${"in use".padEnd(12)} ${inUse.toLocaleString()}${
|
|
18504
|
+
` ${"in use".padEnd(12)} ${inUse.toLocaleString()}${import_chalk55.default.dim(" pre-granted to live requests")}`,
|
|
18208
18505
|
` ${"limit".padEnd(12)} ${limit === null ? "(none)" : limit.toLocaleString()}${bar}`,
|
|
18209
18506
|
` ${"resets".padEnd(12)} ${resets}`,
|
|
18210
18507
|
` ${"spend cap".padEnd(12)} ${usd(q.spend_cap_cents)}${typeof q.spend_cap_cents === "number" ? " / month" : ""}`
|
|
@@ -18225,7 +18522,7 @@ async function runLimitsUsage(project, opts) {
|
|
|
18225
18522
|
const read = () => admin({ method: "GET", path: `/teams/${encodeURIComponent(teamId)}/quota`, summary: `Team quota for ${teamId}` });
|
|
18226
18523
|
const render = (q) => {
|
|
18227
18524
|
const lines = renderTeamQuota(q, teamName ?? teamId);
|
|
18228
|
-
lines.push(
|
|
18525
|
+
lines.push(import_chalk55.default.dim(` ${proj2.projectName}: per-project quota usage is not exposed by the API yet \u2014 this is the team-wide meter that also gates ${proj2.projectName}.`));
|
|
18229
18526
|
return lines;
|
|
18230
18527
|
};
|
|
18231
18528
|
if (!opts.watch) {
|
|
@@ -18242,7 +18539,7 @@ async function runLimitsUsage(project, opts) {
|
|
|
18242
18539
|
else {
|
|
18243
18540
|
process.stdout.write("\x1B[2J\x1B[H");
|
|
18244
18541
|
for (const l of render(q)) console.log(l);
|
|
18245
|
-
console.log(
|
|
18542
|
+
console.log(import_chalk55.default.dim(`
|
|
18246
18543
|
${(/* @__PURE__ */ new Date()).toISOString()} \xB7 every 5 s \xB7 Ctrl-C to stop`));
|
|
18247
18544
|
}
|
|
18248
18545
|
await new Promise((r) => setTimeout(r, 5e3));
|
|
@@ -18251,13 +18548,13 @@ async function runLimitsUsage(project, opts) {
|
|
|
18251
18548
|
async function runThrottleAlias(project, opts) {
|
|
18252
18549
|
const mapped = throttleToLimitsOpts(opts);
|
|
18253
18550
|
const flags = Object.entries(mapped).map(([k, v]) => `--${k.replace(/[A-Z]/g, (c) => "-" + c.toLowerCase())} ${v}`).join(" ");
|
|
18254
|
-
console.error(
|
|
18551
|
+
console.error(import_chalk55.default.yellow(` \`${B.cli} throttle\` is deprecated and will be removed next release \u2014 use: ${B.cli} limits set ${project}${flags ? " " + flags : ""}`));
|
|
18255
18552
|
await runLimitsSet(project, { ...opts, ...mapped });
|
|
18256
18553
|
}
|
|
18257
18554
|
|
|
18258
18555
|
// src/commands/billing.ts
|
|
18259
|
-
var
|
|
18260
|
-
var
|
|
18556
|
+
var import_chalk56 = __toESM(require("chalk"));
|
|
18557
|
+
var import_ora21 = __toESM(require("ora"));
|
|
18261
18558
|
init_admin();
|
|
18262
18559
|
init_resolve();
|
|
18263
18560
|
init_brand();
|
|
@@ -18276,34 +18573,34 @@ var STATUS_WORDS = {
|
|
|
18276
18573
|
unavailable: "unavailable (the wallet root could not be reached)"
|
|
18277
18574
|
};
|
|
18278
18575
|
function statusColour(status2) {
|
|
18279
|
-
if (status2 === "covered" || status2 === "free") return
|
|
18280
|
-
if (status2 === "low" || status2 === "grace" || status2 === "unavailable") return
|
|
18281
|
-
return
|
|
18576
|
+
if (status2 === "covered" || status2 === "free") return import_chalk56.default.green;
|
|
18577
|
+
if (status2 === "low" || status2 === "grace" || status2 === "unavailable") return import_chalk56.default.yellow;
|
|
18578
|
+
return import_chalk56.default.red;
|
|
18282
18579
|
}
|
|
18283
18580
|
function renderBalance(c, teamLabel2) {
|
|
18284
18581
|
const owner = typeof c.balance === "number" || typeof c.available_cents === "number";
|
|
18285
18582
|
const status2 = String(c.status ?? "unavailable");
|
|
18286
|
-
const lines = [`${
|
|
18287
|
-
lines.push(` ${"status".padEnd(14)} ${statusColour(status2)(STATUS_WORDS[status2] ?? status2)}${c.frozen && c.frozen_reason ?
|
|
18583
|
+
const lines = [`${import_chalk56.default.bold(teamLabel2)} \u2014 wallet${c.billing_account_id ? import_chalk56.default.dim(` ${c.billing_account_id}`) : ""}`];
|
|
18584
|
+
lines.push(` ${"status".padEnd(14)} ${statusColour(status2)(STATUS_WORDS[status2] ?? status2)}${c.frozen && c.frozen_reason ? import_chalk56.default.dim(` (${c.frozen_reason})`) : ""}`);
|
|
18288
18585
|
if (!owner) {
|
|
18289
|
-
lines.push(
|
|
18586
|
+
lines.push(import_chalk56.default.dim(" This team runs on another owner's wallet: only its status is visible to you."));
|
|
18290
18587
|
return lines;
|
|
18291
18588
|
}
|
|
18292
18589
|
const available = c.available_cents ?? c.balance ?? 0;
|
|
18293
|
-
lines.push(` ${"available".padEnd(14)} ${
|
|
18294
|
-
lines.push(` ${"in use".padEnd(14)} ${money(c.in_use_cents ?? 0)}${
|
|
18590
|
+
lines.push(` ${"available".padEnd(14)} ${import_chalk56.default.bold(money(available))}`);
|
|
18591
|
+
lines.push(` ${"in use".padEnd(14)} ${money(c.in_use_cents ?? 0)}${import_chalk56.default.dim(" pre-paid to live requests; returns as they finish")}`);
|
|
18295
18592
|
if (typeof c.grace_limit_cents === "number" || typeof c.grace_used_cents === "number") {
|
|
18296
18593
|
const used = c.grace_used_cents ?? 0;
|
|
18297
|
-
lines.push(` ${"grace".padEnd(14)} ${used > 0 ?
|
|
18594
|
+
lines.push(` ${"grace".padEnd(14)} ${used > 0 ? import_chalk56.default.yellow(`${money(used)} owed`) : money(0)} of ${money(c.grace_limit_cents ?? 0)}${import_chalk56.default.dim(" a debt, repaid from the next top-up")}`);
|
|
18298
18595
|
}
|
|
18299
18596
|
if (typeof c.free_requests_remaining === "number" || typeof c.free_turns_remaining === "number") {
|
|
18300
18597
|
lines.push(` ${"free tier".padEnd(14)} ${c.free_requests_remaining ?? 0} requests \xB7 ${c.free_turns_remaining ?? 0} chat turns left`);
|
|
18301
18598
|
}
|
|
18302
18599
|
const ar = c.auto_rebuy;
|
|
18303
|
-
lines.push(` ${"auto-rebuy".padEnd(14)} ${ar?.enabled ?
|
|
18304
|
-
lines.push(` ${"last top-up".padEnd(14)} ${c.last_topup ? `${money(c.last_topup.cents)} on ${c.last_topup.at.replace("T", " ").slice(0, 16)} UTC` :
|
|
18600
|
+
lines.push(` ${"auto-rebuy".padEnd(14)} ${ar?.enabled ? import_chalk56.default.green(`on \u2014 add ${money(ar.amount_cents)} when available drops under ${money(ar.threshold_cents)}`) : import_chalk56.default.dim("off")}`);
|
|
18601
|
+
lines.push(` ${"last top-up".padEnd(14)} ${c.last_topup ? `${money(c.last_topup.cents)} on ${c.last_topup.at.replace("T", " ").slice(0, 16)} UTC` : import_chalk56.default.dim("never")}`);
|
|
18305
18602
|
if (c.terms && c.terms !== "prepaid") lines.push(` ${"terms".padEnd(14)} ${c.terms}`);
|
|
18306
|
-
if (c.closed) lines.push(
|
|
18603
|
+
if (c.closed) lines.push(import_chalk56.default.red(" This wallet is closed."));
|
|
18307
18604
|
return lines;
|
|
18308
18605
|
}
|
|
18309
18606
|
async function readCredits(teamId) {
|
|
@@ -18317,7 +18614,7 @@ async function runBillingBalance(opts) {
|
|
|
18317
18614
|
for (const l of renderBalance(c, teamName ?? teamId)) console.log(l);
|
|
18318
18615
|
const status2 = String(c.status);
|
|
18319
18616
|
if (typeof c.balance === "number" && (status2 === "low" || status2 === "out" || status2 === "grace" || status2 === "frozen")) {
|
|
18320
|
-
console.log(
|
|
18617
|
+
console.log(import_chalk56.default.dim(`
|
|
18321
18618
|
Top up: ${B.cli} billing topup <usd>`));
|
|
18322
18619
|
}
|
|
18323
18620
|
console.log("");
|
|
@@ -18337,18 +18634,18 @@ async function runBillingTopup(amount, opts) {
|
|
|
18337
18634
|
return;
|
|
18338
18635
|
}
|
|
18339
18636
|
if (c && typeof c.balance !== "number") {
|
|
18340
|
-
console.log(
|
|
18637
|
+
console.log(import_chalk56.default.yellow(`
|
|
18341
18638
|
${teamName ?? teamId} runs on another owner's wallet \u2014 only that owner can top it up.`));
|
|
18342
|
-
console.log(
|
|
18639
|
+
console.log(import_chalk56.default.dim(` Ask them, or fund your own: ${url}
|
|
18343
18640
|
`));
|
|
18344
18641
|
return;
|
|
18345
18642
|
}
|
|
18346
18643
|
console.log(`
|
|
18347
|
-
Top up ${
|
|
18348
|
-
console.log(` ${
|
|
18349
|
-
console.log(
|
|
18350
|
-
if (c?.auto_rebuy?.enabled) console.log(
|
|
18351
|
-
else console.log(
|
|
18644
|
+
Top up ${import_chalk56.default.bold(`$${usd2.toFixed(2)}`)} for ${import_chalk56.default.bold(teamName ?? teamId)} in the dashboard (card checkout needs your browser session):`);
|
|
18645
|
+
console.log(` ${import_chalk56.default.cyan(url)}`);
|
|
18646
|
+
console.log(import_chalk56.default.dim(` Enter $${usd2.toFixed(2)} in the wallet panel; the balance updates within seconds of Stripe confirming.`));
|
|
18647
|
+
if (c?.auto_rebuy?.enabled) console.log(import_chalk56.default.dim(` Auto-rebuy is on (${money(c.auto_rebuy.amount_cents)} under ${money(c.auto_rebuy.threshold_cents)}).`));
|
|
18648
|
+
else console.log(import_chalk56.default.dim(` Never run dry: ${B.cli} billing rebuy on --amount 50 --threshold 10`));
|
|
18352
18649
|
if (opts.open) {
|
|
18353
18650
|
const { spawn: spawn5 } = await import("child_process");
|
|
18354
18651
|
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
@@ -18370,15 +18667,15 @@ function usageToCsv(rows) {
|
|
|
18370
18667
|
}
|
|
18371
18668
|
function renderUsageTable(r, hourly) {
|
|
18372
18669
|
const rows = r.rows ?? [];
|
|
18373
|
-
if (!rows.length) return [
|
|
18670
|
+
if (!rows.length) return [import_chalk56.default.dim(" No usage in that window.")];
|
|
18374
18671
|
const w = hourly ? 16 : 10;
|
|
18375
|
-
const lines = [
|
|
18672
|
+
const lines = [import_chalk56.default.dim(` ${"bucket".padEnd(w)} ${"project".padEnd(24)} ${"requests".padStart(10)} ${"free".padStart(8)} ${"cost".padStart(10)}`)];
|
|
18376
18673
|
for (const row of rows) {
|
|
18377
18674
|
const bucket = hourly ? row.bucket.replace("T", " ").slice(0, 16) : row.bucket.slice(0, 10);
|
|
18378
|
-
lines.push(` ${bucket.padEnd(w)} ${(row.project_id || "(no project)").slice(0, 24).padEnd(24)} ${row.requests.toLocaleString().padStart(10)} ${(row.free_used ?? 0).toLocaleString().padStart(8)} ${money(row.cost_cents).padStart(10)}${row.exact === false ?
|
|
18675
|
+
lines.push(` ${bucket.padEnd(w)} ${(row.project_id || "(no project)").slice(0, 24).padEnd(24)} ${row.requests.toLocaleString().padStart(10)} ${(row.free_used ?? 0).toLocaleString().padStart(8)} ${money(row.cost_cents).padStart(10)}${row.exact === false ? import_chalk56.default.dim(" ~") : ""}`);
|
|
18379
18676
|
}
|
|
18380
18677
|
const t = r.totals ?? { requests: rows.reduce((a, x) => a + x.requests, 0), cost_cents: rows.reduce((a, x) => a + x.cost_cents, 0), free_used: rows.reduce((a, x) => a + (x.free_used ?? 0), 0) };
|
|
18381
|
-
lines.push(
|
|
18678
|
+
lines.push(import_chalk56.default.bold(` ${"total".padEnd(w)} ${"".padEnd(24)} ${Number(t.requests ?? 0).toLocaleString().padStart(10)} ${Number(t.free_used ?? 0).toLocaleString().padStart(8)} ${money(t.cost_cents ?? 0).padStart(10)}`));
|
|
18382
18679
|
return lines;
|
|
18383
18680
|
}
|
|
18384
18681
|
var HISTORY_PAGE_MAX = 500;
|
|
@@ -18422,33 +18719,33 @@ async function readTeamLedger(teamId, q) {
|
|
|
18422
18719
|
return out;
|
|
18423
18720
|
}
|
|
18424
18721
|
function renderLedgerLine(l) {
|
|
18425
|
-
const tag = l.pending ?
|
|
18426
|
-
const amt = (l.amount_cents < 0 ?
|
|
18722
|
+
const tag = l.pending ? import_chalk56.default.yellow("pending ") : " ".repeat(8);
|
|
18723
|
+
const amt = (l.amount_cents < 0 ? import_chalk56.default.red : l.amount_cents > 0 ? import_chalk56.default.green : import_chalk56.default.dim)(signedMoney(l.amount_cents).padStart(12));
|
|
18427
18724
|
if (l.class === "flow") {
|
|
18428
18725
|
const reqs = `${(l.requests ?? 0).toLocaleString()} request${l.requests === 1 ? "" : "s"}`;
|
|
18429
|
-
return ` ${tag}${utc(l.ts_from).slice(0, 10).padEnd(16)} ${`${l.label} UTC`.slice(0, 26).padEnd(26)} ${reqs.padStart(16)} ${amt}${l.project_id ?
|
|
18726
|
+
return ` ${tag}${utc(l.ts_from).slice(0, 10).padEnd(16)} ${`${l.label} UTC`.slice(0, 26).padEnd(26)} ${reqs.padStart(16)} ${amt}${l.project_id ? import_chalk56.default.dim(` ${l.project_id}`) : ""}`;
|
|
18430
18727
|
}
|
|
18431
|
-
const bal = typeof l.balance_after_cents === "number" ? `${
|
|
18728
|
+
const bal = typeof l.balance_after_cents === "number" ? `${import_chalk56.default.dim("balance")} ${money(l.balance_after_cents)}` : "";
|
|
18432
18729
|
const extra = [l.label && l.label !== l.kind ? l.label : null, l.project_id].filter(Boolean).join(" \xB7 ");
|
|
18433
|
-
return ` ${tag}${utc(l.ts).padEnd(16)} ${String(l.kind).slice(0, 26).padEnd(26)} ${"".padStart(16)} ${amt}${bal ? ` ${bal}` : ""}${extra ?
|
|
18730
|
+
return ` ${tag}${utc(l.ts).padEnd(16)} ${String(l.kind).slice(0, 26).padEnd(26)} ${"".padStart(16)} ${amt}${bal ? ` ${bal}` : ""}${extra ? import_chalk56.default.dim(` ${extra}`) : ""}`;
|
|
18434
18731
|
}
|
|
18435
18732
|
function renderLedger(r, limit) {
|
|
18436
18733
|
const lines = [];
|
|
18437
|
-
lines.push(
|
|
18438
|
-
if (!r.pending_sync_available) lines.push(
|
|
18734
|
+
lines.push(import_chalk56.default.bold(` Ledger \u2014 newest first${r.window_days ? `, last ${r.window_days} day${r.window_days === 1 ? "" : "s"}` : ""}`) + import_chalk56.default.dim(" (events one line each; usage in 5-minute blocks, UTC)"));
|
|
18735
|
+
if (!r.pending_sync_available) lines.push(import_chalk56.default.yellow(" Pending lines unavailable right now \u2014 the last few minutes of charges may not be listed yet."));
|
|
18439
18736
|
if (r.pending_sync.length) {
|
|
18440
|
-
lines.push(
|
|
18737
|
+
lines.push(import_chalk56.default.yellow(" Pending sync \u2014 made by the wallet, not in the ledger yet:"));
|
|
18441
18738
|
for (const l of r.pending_sync) lines.push(renderLedgerLine({ ...l, pending: true }));
|
|
18442
18739
|
}
|
|
18443
18740
|
if (!r.history_available) {
|
|
18444
|
-
lines.push(
|
|
18741
|
+
lines.push(import_chalk56.default.yellow(" The ledger could not be read right now \u2014 history unknown (not empty). Try again shortly."));
|
|
18445
18742
|
} else if (!r.history.length && !r.pending_sync.length) {
|
|
18446
|
-
lines.push(
|
|
18743
|
+
lines.push(import_chalk56.default.dim(" No ledger lines in that window."));
|
|
18447
18744
|
} else {
|
|
18448
18745
|
for (const l of r.history) lines.push(renderLedgerLine(l));
|
|
18449
18746
|
}
|
|
18450
18747
|
if (r.history_next_before) {
|
|
18451
|
-
lines.push(
|
|
18748
|
+
lines.push(import_chalk56.default.dim(` More lines than --limit ${limit}. Next page: ${B.cli} billing history --before ${r.history_next_before} (or raise --limit)`));
|
|
18452
18749
|
}
|
|
18453
18750
|
return lines;
|
|
18454
18751
|
}
|
|
@@ -18482,9 +18779,9 @@ async function runBillingHistory(opts) {
|
|
|
18482
18779
|
console.log("");
|
|
18483
18780
|
if (ledger) for (const l of renderLedger(ledger, limit)) console.log(l);
|
|
18484
18781
|
console.log("");
|
|
18485
|
-
console.log(
|
|
18782
|
+
console.log(import_chalk56.default.bold(` Usage \u2014 ${opts.raw ? "raw, one row per 5-minute UTC bucket" : "daily rollups"}${opts.since ? ` since ${opts.since}` : ""}${opts.until ? ` until ${opts.until}` : ""}`));
|
|
18486
18783
|
for (const l of renderUsageTable(r, !!opts.raw)) console.log(l);
|
|
18487
|
-
console.log(
|
|
18784
|
+
console.log(import_chalk56.default.dim("\n Exact figures from the money ledger, per project (UTC). Entries the wallet has not sent yet (\u2264 ~5 min) are the pending lines above, not this table. CSV: --format csv\n"));
|
|
18488
18785
|
}
|
|
18489
18786
|
async function runBillingRebuy(mode, opts) {
|
|
18490
18787
|
const which = (mode ?? "status").toLowerCase();
|
|
@@ -18494,12 +18791,12 @@ async function runBillingRebuy(mode, opts) {
|
|
|
18494
18791
|
const c = await readCredits(teamId);
|
|
18495
18792
|
if (opts.json) return void console.log(JSON.stringify({ team_id: teamId, auto_rebuy: c.auto_rebuy ?? null, owner: typeof c.balance === "number" }));
|
|
18496
18793
|
const ar2 = c.auto_rebuy;
|
|
18497
|
-
if (typeof c.balance !== "number") return void console.log(
|
|
18794
|
+
if (typeof c.balance !== "number") return void console.log(import_chalk56.default.dim(`
|
|
18498
18795
|
${teamName ?? teamId} runs on another owner's wallet; auto-rebuy is theirs to set.
|
|
18499
18796
|
`));
|
|
18500
18797
|
console.log(`
|
|
18501
|
-
auto-rebuy ${ar2?.enabled ?
|
|
18502
|
-
console.log(
|
|
18798
|
+
auto-rebuy ${ar2?.enabled ? import_chalk56.default.green("on") : import_chalk56.default.dim("off")}${ar2?.enabled ? ` \u2014 add ${money(ar2.amount_cents)} when available drops under ${money(ar2.threshold_cents)}` : ""}`);
|
|
18799
|
+
console.log(import_chalk56.default.dim(` ${B.cli} billing rebuy on --amount 50 --threshold 10 \xB7 ${B.cli} billing rebuy off
|
|
18503
18800
|
`));
|
|
18504
18801
|
return;
|
|
18505
18802
|
}
|
|
@@ -18515,7 +18812,7 @@ async function runBillingRebuy(mode, opts) {
|
|
|
18515
18812
|
throw new UsageError(`--amount must be between $${TOPUP_MIN_USD} and $${TOPUP_MAX_USD} (one card charge).`);
|
|
18516
18813
|
}
|
|
18517
18814
|
}
|
|
18518
|
-
const spinner = (0,
|
|
18815
|
+
const spinner = (0, import_ora21.default)(`Auto-rebuy ${which}...`).start();
|
|
18519
18816
|
let r;
|
|
18520
18817
|
try {
|
|
18521
18818
|
r = await admin({ method: "PUT", path: `/credits/auto-rebuy?teamId=${encodeURIComponent(teamId)}`, body, summary: `Auto-rebuy ${which}` });
|
|
@@ -18528,159 +18825,8 @@ async function runBillingRebuy(mode, opts) {
|
|
|
18528
18825
|
if (opts.json) console.log(JSON.stringify(r ?? { auto_rebuy: body }));
|
|
18529
18826
|
}
|
|
18530
18827
|
|
|
18531
|
-
// src/
|
|
18532
|
-
|
|
18533
|
-
var import_ora21 = __toESM(require("ora"));
|
|
18534
|
-
init_caller();
|
|
18535
|
-
var HEADER_NAME = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
|
|
18536
|
-
var ruleNameFor = (header) => `set header ${header.toLowerCase()}`;
|
|
18537
|
-
async function target(project, opts) {
|
|
18538
|
-
const caller = requireCaller();
|
|
18539
|
-
const { teamId } = await resolveActingTeam(caller, opts.team);
|
|
18540
|
-
const proj2 = await resolveProjectVia(caller, teamId, project, opts.apiversion);
|
|
18541
|
-
const base2 = `/projects/${encodeURIComponent(proj2.projectId)}/${encodeURIComponent(proj2.apiVersion)}/transforms`;
|
|
18542
|
-
return { caller, proj: proj2, base: base2 };
|
|
18543
|
-
}
|
|
18544
|
-
async function listRules(t) {
|
|
18545
|
-
const out = await producer(t.caller, {
|
|
18546
|
-
method: "GET",
|
|
18547
|
-
path: t.base,
|
|
18548
|
-
summary: `List transforms for ${t.proj.projectName}`
|
|
18549
|
-
});
|
|
18550
|
-
return out?.rules ?? [];
|
|
18551
|
-
}
|
|
18552
|
-
function describe(r) {
|
|
18553
|
-
const a = r.action ?? { type: "?" };
|
|
18554
|
-
if (a.type === "hardcode") {
|
|
18555
|
-
const secret = !a.value || /^abzsec:|^__ABZSEC_|^•+$/.test(a.value);
|
|
18556
|
-
return `set ${a.destination} = ${secret ? "(secret)" : a.value}`;
|
|
18557
|
-
}
|
|
18558
|
-
if (a.type === "remove") return `remove ${a.field}`;
|
|
18559
|
-
if (a.type === "map") return `map ${a.source} \u2192 ${a.destination}${a.lookup?.mapping_id ? " (through a mapping table)" : ""}`;
|
|
18560
|
-
return a.type;
|
|
18561
|
-
}
|
|
18562
|
-
async function runTransformList(project, opts) {
|
|
18563
|
-
const t = await target(project, opts);
|
|
18564
|
-
const rules = await listRules(t);
|
|
18565
|
-
if (opts.json) {
|
|
18566
|
-
console.log(JSON.stringify(rules, null, 2));
|
|
18567
|
-
return;
|
|
18568
|
-
}
|
|
18569
|
-
if (!rules.length) {
|
|
18570
|
-
console.log(import_chalk56.default.dim(`No transforms on ${t.proj.projectName} v${t.proj.apiVersion}.`));
|
|
18571
|
-
return;
|
|
18572
|
-
}
|
|
18573
|
-
for (const r of rules) {
|
|
18574
|
-
console.log(`${r.enabled ? import_chalk56.default.green("\u25CF") : import_chalk56.default.gray("\u25CB")} ${import_chalk56.default.bold(r.name)} ${import_chalk56.default.dim(`[${r.phase}] ${r.id}`)}`);
|
|
18575
|
-
console.log(` ${describe(r)}`);
|
|
18576
|
-
}
|
|
18577
|
-
}
|
|
18578
|
-
async function runTransformSetHeader(project, header, opts) {
|
|
18579
|
-
if (!HEADER_NAME.test(header)) throw new Error(`"${header}" is not a valid header name.`);
|
|
18580
|
-
if (opts.value === void 0 === (opts.valueEnv === void 0)) {
|
|
18581
|
-
throw new Error("Pass exactly one of --value <value> or --value-env <VAR> (reads the value from that environment variable, so it stays out of your shell history).");
|
|
18582
|
-
}
|
|
18583
|
-
const value = opts.value ?? process.env[opts.valueEnv];
|
|
18584
|
-
if (!value) throw new Error(`Environment variable ${opts.valueEnv} is empty or not set.`);
|
|
18585
|
-
const t = await target(project, opts);
|
|
18586
|
-
const name = ruleNameFor(header);
|
|
18587
|
-
const action = {
|
|
18588
|
-
type: "hardcode",
|
|
18589
|
-
destination: `header:${header}`,
|
|
18590
|
-
value,
|
|
18591
|
-
// Encrypted at rest and masked on every read; main-proxy decrypts it only
|
|
18592
|
-
// on the way to the upstream.
|
|
18593
|
-
...opts.secret ? { value_secret: true } : {}
|
|
18594
|
-
};
|
|
18595
|
-
const existing = (await listRules(t)).find((r) => r.name === name && r.phase === "request");
|
|
18596
|
-
const summary = `${existing ? "Update" : "Add"} transform: send ${header} to the upstream`;
|
|
18597
|
-
const spinner = opts.json ? null : (0, import_ora21.default)(summary + "...").start();
|
|
18598
|
-
try {
|
|
18599
|
-
const out = await producer(t.caller, existing ? { method: "PUT", path: `${t.base}/${encodeURIComponent(existing.id)}`, body: { action, enabled: true }, summary } : { method: "POST", path: t.base, body: { name, action, phase: "request", enabled: true }, summary });
|
|
18600
|
-
spinner?.succeed(`${summary} \u2014 ${t.proj.projectName} v${t.proj.apiVersion}${opts.secret ? " (stored encrypted)" : ""}.`);
|
|
18601
|
-
if (opts.json) console.log(JSON.stringify(out ?? { ok: true }));
|
|
18602
|
-
else console.log(import_chalk56.default.dim(` Every call the proxy forwards now carries ${header}. Your backend can reject calls without it.`));
|
|
18603
|
-
} catch (err) {
|
|
18604
|
-
spinner?.fail(summary + " failed.");
|
|
18605
|
-
throw err;
|
|
18606
|
-
}
|
|
18607
|
-
}
|
|
18608
|
-
async function runTransformRemove(project, nameOrId, opts) {
|
|
18609
|
-
const t = await target(project, opts);
|
|
18610
|
-
const rules = await listRules(t);
|
|
18611
|
-
const want = nameOrId.toLowerCase();
|
|
18612
|
-
const rule = rules.find((r) => r.id === nameOrId || r.name.toLowerCase() === want || r.name === ruleNameFor(nameOrId));
|
|
18613
|
-
if (!rule) {
|
|
18614
|
-
throw new Error(`No transform "${nameOrId}" on ${t.proj.projectName}.` + (rules.length ? ` Transforms: ${rules.map((r) => r.name).join(", ")}` : ""));
|
|
18615
|
-
}
|
|
18616
|
-
await producer(t.caller, {
|
|
18617
|
-
method: "DELETE",
|
|
18618
|
-
path: `${t.base}/${encodeURIComponent(rule.id)}`,
|
|
18619
|
-
summary: `Remove transform "${rule.name}"`
|
|
18620
|
-
});
|
|
18621
|
-
if (opts.json) console.log(JSON.stringify({ ok: true, removed: rule.id }));
|
|
18622
|
-
else console.log(`${import_chalk56.default.green("\u2713")} Removed transform ${import_chalk56.default.bold(rule.name)} from ${t.proj.projectName}.`);
|
|
18623
|
-
}
|
|
18624
|
-
function tablesBase(t) {
|
|
18625
|
-
return t.base.replace(/\/transforms$/, "/mappings");
|
|
18626
|
-
}
|
|
18627
|
-
async function listTables(t) {
|
|
18628
|
-
const out = await producer(t.caller, {
|
|
18629
|
-
method: "GET",
|
|
18630
|
-
path: tablesBase(t),
|
|
18631
|
-
summary: `List mapping tables for ${t.proj.projectName}`
|
|
18632
|
-
});
|
|
18633
|
-
return out?.tables ?? [];
|
|
18634
|
-
}
|
|
18635
|
-
function parseEntries(pairs) {
|
|
18636
|
-
return pairs.map((p) => {
|
|
18637
|
-
const i = p.indexOf("=");
|
|
18638
|
-
if (i <= 0) throw new Error(`"${p}" is not a from=to pair (e.g. FR=EUR).`);
|
|
18639
|
-
return { from: p.slice(0, i), to: p.slice(i + 1) };
|
|
18640
|
-
});
|
|
18641
|
-
}
|
|
18642
|
-
var FIELD_REF = /^(header|query|param|bodyvar|jwt):.+/;
|
|
18643
|
-
async function runTransformMapping(project, table, pairs, opts) {
|
|
18644
|
-
if (!pairs.length) throw new Error("Give at least one from=to pair, e.g. FR=EUR US=USD.");
|
|
18645
|
-
const entries2 = parseEntries(pairs);
|
|
18646
|
-
const t = await target(project, opts);
|
|
18647
|
-
const existing = (await listTables(t)).find((m) => m.name === table);
|
|
18648
|
-
const body = { name: table, entries: entries2, ...opts.secret ? { encrypt_values: true } : {} };
|
|
18649
|
-
const summary = `${existing ? "Replace" : "Create"} mapping table "${table}" (${entries2.length} entr${entries2.length === 1 ? "y" : "ies"})`;
|
|
18650
|
-
const out = await producer(t.caller, existing ? { method: "PUT", path: `${tablesBase(t)}/${encodeURIComponent(existing.id)}`, body, summary } : { method: "POST", path: tablesBase(t), body, summary });
|
|
18651
|
-
if (opts.json) {
|
|
18652
|
-
console.log(JSON.stringify(out ?? { ok: true }));
|
|
18653
|
-
return;
|
|
18654
|
-
}
|
|
18655
|
-
console.log(`${import_chalk56.default.green("\u2714")} ${summary} \u2014 ${t.proj.projectName} v${t.proj.apiVersion}.`);
|
|
18656
|
-
for (const e of entries2) console.log(import_chalk56.default.dim(` ${e.from} \u2192 ${opts.secret ? "(secret)" : e.to}`));
|
|
18657
|
-
}
|
|
18658
|
-
async function runTransformMap(project, source, destination, opts) {
|
|
18659
|
-
if (!FIELD_REF.test(source)) throw new Error(`Source "${source}" must look like header:X-Country, query:country or bodyvar:order.country.`);
|
|
18660
|
-
if (!/^(header|query|bodyvar):.+/.test(destination)) throw new Error(`Destination "${destination}" must look like header:X-Currency, query:currency or bodyvar:order.currency.`);
|
|
18661
|
-
if (!opts.table) throw new Error("--table <name> is required: the mapping table to look the value up in (create it with `transform mapping`).");
|
|
18662
|
-
const onNoMatch = opts.onNoMatch ?? "skip";
|
|
18663
|
-
if (!["skip", "fail", "passthrough"].includes(onNoMatch)) throw new Error("--on-no-match is skip, fail or passthrough.");
|
|
18664
|
-
const t = await target(project, opts);
|
|
18665
|
-
const table = (await listTables(t)).find((m) => m.name === opts.table);
|
|
18666
|
-
if (!table) throw new Error(`No mapping table "${opts.table}" on ${t.proj.projectName}. Create it: npx apiblaze transform mapping ${project} ${opts.table} FR=EUR \u2026`);
|
|
18667
|
-
const name = `map ${source} to ${destination}`;
|
|
18668
|
-
const action = {
|
|
18669
|
-
type: "map",
|
|
18670
|
-
source,
|
|
18671
|
-
destination,
|
|
18672
|
-
lookup: { mapping_id: table.id, on_no_match: onNoMatch }
|
|
18673
|
-
};
|
|
18674
|
-
const existing = (await listRules(t)).find((r) => r.name === name && r.phase === "request");
|
|
18675
|
-
const summary = `${existing ? "Update" : "Add"} transform: ${source} \u2192 ${destination} through "${table.name}"`;
|
|
18676
|
-
const out = await producer(t.caller, existing ? { method: "PUT", path: `${t.base}/${encodeURIComponent(existing.id)}`, body: { action, enabled: true }, summary } : { method: "POST", path: t.base, body: { name, action, phase: "request", enabled: true }, summary });
|
|
18677
|
-
if (opts.json) {
|
|
18678
|
-
console.log(JSON.stringify(out ?? { ok: true }));
|
|
18679
|
-
return;
|
|
18680
|
-
}
|
|
18681
|
-
console.log(`${import_chalk56.default.green("\u2714")} ${summary} \u2014 ${t.proj.projectName} v${t.proj.apiVersion}.`);
|
|
18682
|
-
console.log(import_chalk56.default.dim(` A value not in the table: ${onNoMatch === "skip" ? "nothing is written" : onNoMatch === "fail" ? "the call is refused" : "it is copied as-is"}.`));
|
|
18683
|
-
}
|
|
18828
|
+
// src/cli.ts
|
|
18829
|
+
init_transform();
|
|
18684
18830
|
|
|
18685
18831
|
// src/commands/config-browse.ts
|
|
18686
18832
|
var import_chalk63 = __toESM(require("chalk"));
|
|
@@ -23341,7 +23487,7 @@ function buildProgram() {
|
|
|
23341
23487
|
program2.command("create").description(descriptionFor({
|
|
23342
23488
|
apiblaze: `Put an app that is already DEPLOYED behind ${B.product}: a public URL and an MCP address for AI agents in front of your URL or OpenAPI spec. Included: API keys, sign-in, ownership rules, rate limits. No login needed. (App on this machine? Use \`dev\`.) Add --compliant to make it a Claude connector, or a ChatGPT plugin with --compliant chatgpt, that meets the directory's policies.`,
|
|
23343
23489
|
mcpblaze: "Make an MCP server from a deployed API's OpenAPI spec or URL: a connector address, sign-in and per-user rules included. No login needed. (App on this machine? Use `dev`.) Add --compliant for a Claude connector (or a ChatGPT plugin with --compliant chatgpt) that meets the directory policies; it says what is left at every step and hands you the page to finish."
|
|
23344
|
-
})).option("--name <name>", B.cli === "mcpblaze" ? "MCP server name (becomes <name>-<tenant>.mcpblaze.com)" : "Proxy name (becomes <name>.abz.run)").option("--target <url|file>", descriptionFor({ apiblaze: "What to proxy \u2014 pass ANY of: a target server base URL (https://httpbin.org), a local OpenAPI file (./openapi.yaml), or a remote OpenAPI URL (https://acme.com/openapi.yaml). Spec inputs are detected automatically; routes, API version and environments then come from the spec", mcpblaze: "What to serve as an MCP server \u2014 pass ANY of: a target server base URL (https://httpbin.org), a local OpenAPI file (./openapi.yaml), or a remote OpenAPI URL (https://acme.com/openapi.yaml). Spec inputs are detected automatically; routes, API version and environments then come from the spec" })).addOption(new import_commander.Option("--openapi <file|url>", "Deprecated alias \u2014 --target now detects spec files/URLs itself").hideHelp()).addOption(new import_commander.Option("--openapispec <file|url>", "Deprecated alias for --openapi").hideHelp()).option("--team <id|name>", "Team to create under (defaults to your active team)").option("--auth <type>", "Open ONE door only: api_key | oauth (sign in with GitHub, APIblaze-hosted) | none. Omitted = both doors.").option("--apikey", "API-key door only (no sign-in): the bootstrap keys are minted and printed; callers send X-API-Key, agents connect with a key.").option("--oauth [config]", `Sign-in door only (no API key). Bare = APIblaze-hosted GitHub sign-in. '{"iss","aud","jwks"}' = trust YOUR hosted login's JWTs. '{"provider","clientId","clientSecret"?}' = APIblaze-hosted login page with YOUR OAuth app (github \xB7 google \xB7 microsoft \xB7 facebook \xB7 auth0 \xB7 oidc); the secret is optional except for github and facebook. '{"provider":"oidc","issuer":"https://acme.okta.com/oauth2/default","clientId":"\u2026"}' = any OpenID Connect IdP by issuer (Okta \xB7 Entra \xB7 Keycloak \xB7 Cognito \xB7 \u2026).`).option("--identified", "Require every call to identify its end user (X-End-User-Id from a backend key, or a login token \u2014 a client key alone cannot); unattributed calls are rejected").option("--iam", `Turn IAM on for the ${B.thingShort}'s tenant so users & groups apply to identified calls`).option("--apiversion <version>", `API version to create (e.g. 2.0.0). Creating a new version of a ${B.thingShort} you own adds a version to the existing project.`).option("--tenant <slug>", `Tenant to attach the ${B.thingShort} to (created if new). Omitted \u2192 your team's most-recently-used tenant.`).option("--product <slug>", "Product tag to group this project under in the portal (team-scoped; anonymous create). Defaults to your team's existing/placeholder tag.").option("--display-name <name>", "Human-friendly display name").option("--subdomain <slug>", "Explicit subdomain (defaults to --name)").option("--config <file>", "JSON file with the full request body (anonymous create): requests_auth, login providers + client/server token types, scopes, callback URLs, etc. See apiblaze_anonymous.yaml. Flags override its fields.").option("-y, --yes", "Skip the confirmation prompt").option("--auto", `Zero prompts, start to finish: generates the ${B.thingShort} name when --name is omitted (from the target host, salted), takes every confirmation as yes, auto-picks the tenant. Implies --yes.`).option("--new-session", "Start a fresh anonymous session (do not group with prior anonymous creates)").option("--json", "Output machine-readable JSON (non-interactive)").option("--compliant [directories...]", `Directory-ready in one command, to the letter of that directory's policies: create the ${B.thingShort} from --target (an OpenAPI URL/file), apply the safe fixes, set up the custom MCP domain (--domain, one CNAME), ask for every listing answer with the spec's proposal pre-filled (cut to the portal's limits), create the reviewer account, write the submission packet. Bare = claude; or claude \xB7 chatgpt \xB7 all. Ends with the compliance table: every rule the listing satisfies and the policy page it comes from. Same as \`${B.cli} connector <spec> --compliant \u2026\`.`).addOption(new import_commander.Option("--policy [directories...]", "Older spelling of --compliant").hideHelp()).option("--domain <host>", "With --compliant: the MCP server's custom hostname (mcp.<your domain>); asked for when missing, suggested from the spec").option("--wait <minutes>", "With --compliant: how long to wait for the custom domain to activate once its CNAME is in place (default 15; 0 = do not wait)").option("--auth-mode <mode>", "With --compliant: how directory clients get in. signin (default) = OAuth sign-in on the connector, API keys alongside; open = no sign-in at all, for public read-only data (Anthropic allows it) \u2014 the API host keeps its own doors. Asked once when every tool is read-only.").option("--data-handling <mode>", "Compliant: whose API this is (own \xB7 proxied + --data-handling-statement \xB7 third-party); never pre-filled").option("--data-handling-statement <text>", "Compliant, proxied: the permission you hold").option("--health-data <yes|no>", "Compliant: the tools handle health data (never pre-filled)").option("--sponsored-content <yes|no>", "Compliant: tool results carry sponsored content (never pre-filled)").option("--no-exercise", 'Compliant: skip the "Run every tool now?" step').option("--seed", "Sign-in mode: when a read returns nothing for the reviewer, create a few records through the create tools and keep them (asked when omitted)").option("--no-seed", "Never seed the reviewer account").option("--no-email", "Compliant: no email at the end of the run").option("--directory-ready", "Make it directory-ready for the Claude and ChatGPT directories: OAuth door, custom-domain checks, listing fields; the readiness report runs after the create and a blocker exits 1").option("--privacy-url <url>", "Directory listing: public https privacy policy").option("--support <email|url>", "Directory listing: support contact").option("--docs-url <url>", "Directory listing: public documentation URL (the developer portal by default)").option("--prompt <text>", "Directory listing: an example prompt (repeat the flag; three are needed)", collect, []).option("--server-name <name>", 'Directory listing: the name (2\u201340 chars, no "MCP"/"Plugin" suffix)').option("--tagline <text>", "Directory listing: one-line tagline").option("--description <text>", "Directory listing: description").option("--icon-url <url>", "Directory listing: square icon (https)").option("--slug <slug>", "Claude: the listing slug (lowercase-with-hyphens; permanent once published)").option("--high-risk <list>", "Claude policy 1A: none, or any of legal,healthcare,insurance,finance").option("--open-world <yes|no>", "Every tool: reaches public or open-ended data (yes) or only the signed-in account's (no); asked once when omitted").option("--no-watch", "Do not turn on the nightly re-check email when the listing is green").option("--no-wait-login", "Sign-in mode: do not wait for a Claude sign-in before the verdict").option("--mcp-app", "Claude: the listing is an MCP App (the host then advertises the UI extension; needs 3\u20135 --screenshot)").option("--no-mcp-app", "Claude: a plain connector (default)").option("--screenshot <url=prompt>", "Claude MCP App: a carousel screenshot (PNG \u2265 1000 px wide) and the prompt that produced it (repeatable, 3\u20135)", collect, []).option("--vuln-contact <email|url>", "Directory listing: vulnerability-disclosure contact").option("--attest <id>", "Attest (repeatable): guidelines \xB7 own-backend \xB7 no-financial \xB7 no-genai-media \xB7 no-prompt-injection \xB7 no-conversation-data \xB7 public-docs (Claude's seven) \xB7 no-digital-goods \xB7 content-13-no-ads \xB7 no-restricted-data (ChatGPT)", collect, []).option("--allowed-origin <origin>", "Directory mode: an extra browser origin allowed on the MCP host (repeatable)", collect, []).option("--openai-challenge <token>", "ChatGPT: the domain-verification token the OpenAI portal shows under Plugins \u2192 Connect MCP server").action(async (opts) => {
|
|
23490
|
+
})).option("--name <name>", B.cli === "mcpblaze" ? "MCP server name (becomes <name>-<tenant>.mcpblaze.com)" : "Proxy name (becomes <name>.abz.run)").option("--target <url|file>", descriptionFor({ apiblaze: "What to proxy \u2014 pass ANY of: a target server base URL (https://httpbin.org), a local OpenAPI file (./openapi.yaml), or a remote OpenAPI URL (https://acme.com/openapi.yaml). Spec inputs are detected automatically; routes, API version and environments then come from the spec", mcpblaze: "What to serve as an MCP server \u2014 pass ANY of: a target server base URL (https://httpbin.org), a local OpenAPI file (./openapi.yaml), or a remote OpenAPI URL (https://acme.com/openapi.yaml). Spec inputs are detected automatically; routes, API version and environments then come from the spec" })).addOption(new import_commander.Option("--openapi <file|url>", "Deprecated alias \u2014 --target now detects spec files/URLs itself").hideHelp()).addOption(new import_commander.Option("--openapispec <file|url>", "Deprecated alias for --openapi").hideHelp()).option("--team <id|name>", "Team to create under (defaults to your active team)").option("--auth <type>", "Open ONE door only: api_key | oauth (sign in with GitHub, APIblaze-hosted) | none. Omitted = both doors.").option("--apikey", "API-key door only (no sign-in): the bootstrap keys are minted and printed; callers send X-API-Key, agents connect with a key.").option("--oauth [config]", `Sign-in door only (no API key). Bare = APIblaze-hosted GitHub sign-in. '{"iss","aud","jwks"}' = trust YOUR hosted login's JWTs. '{"provider","clientId","clientSecret"?}' = APIblaze-hosted login page with YOUR OAuth app (github \xB7 google \xB7 microsoft \xB7 facebook \xB7 auth0 \xB7 oidc); the secret is optional except for github and facebook. '{"provider":"oidc","issuer":"https://acme.okta.com/oauth2/default","clientId":"\u2026"}' = any OpenID Connect IdP by issuer (Okta \xB7 Entra \xB7 Keycloak \xB7 Cognito \xB7 \u2026).`).option("--identified", "Require every call to identify its end user (X-End-User-Id from a backend key, or a login token \u2014 a client key alone cannot); unattributed calls are rejected").option("--iam", `Turn IAM on for the ${B.thingShort}'s tenant so users & groups apply to identified calls`).option("--apiversion <version>", `API version to create (e.g. 2.0.0). Creating a new version of a ${B.thingShort} you own adds a version to the existing project.`).option("--tenant <slug>", `Tenant to attach the ${B.thingShort} to (created if new). Omitted \u2192 your team's most-recently-used tenant.`).option("--product <slug>", "Product tag to group this project under in the portal (team-scoped; anonymous create). Defaults to your team's existing/placeholder tag.").option("--display-name <name>", "Human-friendly display name").option("--subdomain <slug>", "Explicit subdomain (defaults to --name)").option("--config <file>", "JSON file with the full request body (anonymous create): requests_auth, login providers + client/server token types, scopes, callback URLs, etc. See apiblaze_anonymous.yaml. Flags override its fields.").option("-y, --yes", "Skip the confirmation prompt").option("--auto", `Zero prompts, start to finish: generates the ${B.thingShort} name when --name is omitted (from the target host, salted), takes every confirmation as yes, auto-picks the tenant. Implies --yes.`).option("--new-session", "Start a fresh anonymous session (do not group with prior anonymous creates)").option("--json", "Output machine-readable JSON (non-interactive)").option("--compliant [directories...]", `Directory-ready in one command, to the letter of that directory's policies: create the ${B.thingShort} from --target (an OpenAPI URL/file), apply the safe fixes, set up the custom MCP domain (--domain, one CNAME), ask for every listing answer with the spec's proposal pre-filled (cut to the portal's limits), create the reviewer account, write the submission packet. Bare = claude; or claude \xB7 chatgpt \xB7 all. Ends with the compliance table: every rule the listing satisfies and the policy page it comes from. Same as \`${B.cli} connector <spec> --compliant \u2026\`.`).addOption(new import_commander.Option("--policy [directories...]", "Older spelling of --compliant").hideHelp()).option("--domain <host>", "With --compliant: the MCP server's custom hostname (mcp.<your domain>); asked for when missing, suggested from the spec").option("--wait <minutes>", "With --compliant: how long to wait for the custom domain to activate once its CNAME is in place (default 15; 0 = do not wait)").option("--target-url <url>", "With --compliant: Where the app runs in production (https): a listing needs the hosted app, not a tunnel; asked when missing").option("--upstream-header <header>", 'With --compliant: "Name: value", the header the backend needs to accept these calls, stored encrypted; asked when missing').option("--upstream-header-env <name=var>", "With --compliant: The same with the value read from an environment variable, so it stays out of your shell history").option("--upstream-open", "With --compliant: The backend checks its own credentials: no header").option("--auth-mode <mode>", "With --compliant: how directory clients get in. signin (default) = OAuth sign-in on the connector, API keys alongside; open = no sign-in at all, for public read-only data (Anthropic allows it) \u2014 the API host keeps its own doors. Asked once when every tool is read-only.").option("--data-handling <mode>", "Compliant: whose API this is (own \xB7 proxied + --data-handling-statement \xB7 third-party); never pre-filled").option("--data-handling-statement <text>", "Compliant, proxied: the permission you hold").option("--health-data <yes|no>", "Compliant: the tools handle health data (never pre-filled)").option("--sponsored-content <yes|no>", "Compliant: tool results carry sponsored content (never pre-filled)").option("--no-exercise", 'Compliant: skip the "Run every tool now?" step').option("--seed", "Sign-in mode: when a read returns nothing for the reviewer, create a few records through the create tools and keep them (asked when omitted)").option("--no-seed", "Never seed the reviewer account").option("--no-email", "Compliant: no email at the end of the run").option("--directory-ready", "Make it directory-ready for the Claude and ChatGPT directories: OAuth door, custom-domain checks, listing fields; the readiness report runs after the create and a blocker exits 1").option("--privacy-url <url>", "Directory listing: public https privacy policy").option("--support <email|url>", "Directory listing: support contact").option("--docs-url <url>", "Directory listing: public documentation URL (the developer portal by default)").option("--prompt <text>", "Directory listing: an example prompt (repeat the flag; three are needed)", collect, []).option("--server-name <name>", 'Directory listing: the name (2\u201340 chars, no "MCP"/"Plugin" suffix)').option("--tagline <text>", "Directory listing: one-line tagline").option("--description <text>", "Directory listing: description").option("--icon-url <url>", "Directory listing: square icon (https)").option("--slug <slug>", "Claude: the listing slug (lowercase-with-hyphens; permanent once published)").option("--high-risk <list>", "Claude policy 1A: none, or any of legal,healthcare,insurance,finance").option("--open-world <yes|no>", "Every tool: reaches public or open-ended data (yes) or only the signed-in account's (no); asked once when omitted").option("--no-watch", "Do not turn on the nightly re-check email when the listing is green").option("--no-wait-login", "Sign-in mode: do not wait for a Claude sign-in before the verdict").option("--mcp-app", "Claude: the listing is an MCP App (the host then advertises the UI extension; needs 3\u20135 --screenshot)").option("--no-mcp-app", "Claude: a plain connector (default)").option("--screenshot <url=prompt>", "Claude MCP App: a carousel screenshot (PNG \u2265 1000 px wide) and the prompt that produced it (repeatable, 3\u20135)", collect, []).option("--vuln-contact <email|url>", "Directory listing: vulnerability-disclosure contact").option("--attest <id>", "Attest (repeatable): guidelines \xB7 own-backend \xB7 no-financial \xB7 no-genai-media \xB7 no-prompt-injection \xB7 no-conversation-data \xB7 public-docs (Claude's seven) \xB7 no-digital-goods \xB7 content-13-no-ads \xB7 no-restricted-data (ChatGPT)", collect, []).option("--allowed-origin <origin>", "Directory mode: an extra browser origin allowed on the MCP host (repeatable)", collect, []).option("--openai-challenge <token>", "ChatGPT: the domain-verification token the OpenAI portal shows under Plugins \u2192 Connect MCP server").action(async (opts) => {
|
|
23345
23491
|
try {
|
|
23346
23492
|
const compliant = opts.compliant ?? opts.policy;
|
|
23347
23493
|
if (compliant !== void 0 && compliant !== false) {
|
|
@@ -23367,7 +23513,7 @@ function buildProgram() {
|
|
|
23367
23513
|
const connector = program2.command("connector").description(descriptionFor({
|
|
23368
23514
|
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)`,
|
|
23369
23515
|
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."
|
|
23370
|
-
})).argument("[project-or-spec]", `The ${B.thingShort} (project name), or an OpenAPI URL/file to create a directory-ready ${B.thingShort} from first`).option("--domain <host>", "Custom hostname for the MCP server (mcp.<your domain>); asked for when missing, suggested from the spec").option("--wait <minutes>", "How long to wait for the custom domain to activate once its CNAME is in place (default 15; 0 = do not wait)").option("--auth-mode <mode>", "How directory clients get in: signin (default) = OAuth sign-in on the connector, API keys alongside; open = no sign-in at all, for public read-only data (Anthropic allows it) \u2014 the API host keeps its own doors. Asked once when every tool is read-only.").option("--name <name>", `When creating from a spec: the ${B.thingShort} name (default: derived from the spec host)`).option("--privacy-url <url>", "Public https privacy policy").option("--support <email|url>", "Support contact").option("--docs-url <url>", "Public documentation URL").option("--prompt <text>", "An example prompt (repeatable; three are needed)", collect, []).option("--server-name <name>", 'Listing name (2\u201340 chars, no "MCP"/"Plugin" suffix)').option("--tagline <text>", "One-line tagline").option("--description <text>", "Description").option("--icon-url <url>", "Square icon (https)").option("--slug <slug>", "Claude: the listing slug (lowercase-with-hyphens; permanent once published)").option("--high-risk <list>", "Claude policy 1A: none, or any of legal,healthcare,insurance,finance").option("--mcp-app", "Claude: the listing is an MCP App (needs 3\u20135 --screenshot)").option("--no-mcp-app", "Claude: a plain connector (default)").option("--screenshot <url=prompt>", "Claude MCP App: a carousel screenshot and the prompt that produced it (repeatable, 3\u20135)", collect, []).option("--vuln-contact <email|url>", "Vulnerability-disclosure contact").option("--attest <id>", "Attest (repeatable): guidelines \xB7 own-backend \xB7 no-financial \xB7 no-genai-media \xB7 no-prompt-injection \xB7 no-conversation-data \xB7 public-docs (Claude's seven) \xB7 no-digital-goods \xB7 content-13-no-ads \xB7 no-restricted-data (ChatGPT). Alone, this only records the attestation.", collect, []).option("--allowed-origin <origin>", "An extra browser origin allowed on the MCP host (repeatable)", collect, []).option("--openai-challenge <token>", "ChatGPT: the domain-verification token the OpenAI portal shows under Plugins \u2192 Connect MCP server; served verbatim at /.well-known/openai-apps-challenge on your MCP domain").option("--data-handling <mode>", "Whose API this is (portal step 7): own \xB7 proxied (add --data-handling-statement) \xB7 third-party. Never pre-filled.").option("--data-handling-statement <text>", "For proxied: the permission you hold (who granted what, and when)").option("--health-data <yes|no>", "The tools handle health data (never pre-filled; yes adds the human-in-the-loop note to the packet)").option("--sponsored-content <yes|no>", "Tool results carry sponsored content (never pre-filled; yes is an unsupported use case)").option("--website-url <url>", "OpenAI's form: the public website (https)").option("--support-url <url>", "OpenAI's form: the support page (https; a URL, not an email)").option("--terms-url <url>", "OpenAI's form: the terms of service page (https)").option("--developer-name <name>", "OpenAI's form: the developer name (\u2264 80; the directory shows your verified identity)").option("--chatgpt-category <name>", "OpenAI's form: one of Productivity \xB7 Creativity \xB7 Developer Tools \xB7 Business & Operations \xB7 Data & Analytics \xB7 Communication \xB7 Education & Research \xB7 Security \xB7 Finance \xB7 Healthcare \xB7 Travel \xB7 Entertainment \xB7 Other").option("--demo-recording-url <url>", "OpenAI's form: the demo recording (required for MCP review)").option("--release-notes <text>", "OpenAI's form: release notes for this submission").option("--short-description <text>", "OpenAI's form: the 30-character subtitle (Claude's tagline stays \u2264 200)").option("--countries <codes>", "OpenAI's form: ISO country codes, comma-separated (default: everywhere)", (v) => v.split(",").map((s) => s.trim()).filter(Boolean)).option("--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) => {
|
|
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) => {
|
|
23371
23517
|
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>`);
|
|
23372
23518
|
await runConnector(project, opts);
|
|
23373
23519
|
}));
|