apiblaze 0.21.66 → 0.21.67
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 +712 -317
- 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.67";
|
|
38
38
|
}
|
|
39
39
|
});
|
|
40
40
|
|
|
@@ -5166,6 +5166,12 @@ var init_key = __esm({
|
|
|
5166
5166
|
});
|
|
5167
5167
|
|
|
5168
5168
|
// src/lib/directory-probes.ts
|
|
5169
|
+
var directory_probes_exports = {};
|
|
5170
|
+
__export(directory_probes_exports, {
|
|
5171
|
+
PROBE_TIMEOUT_MS: () => PROBE_TIMEOUT_MS,
|
|
5172
|
+
asMetadataUrl: () => asMetadataUrl,
|
|
5173
|
+
probeDirectoryTarget: () => probeDirectoryTarget
|
|
5174
|
+
});
|
|
5169
5175
|
async function probe(url, init, deps) {
|
|
5170
5176
|
const f2 = deps.fetch ?? fetch;
|
|
5171
5177
|
const now = deps.now ?? (() => Date.now());
|
|
@@ -5311,6 +5317,7 @@ __export(directory_exports, {
|
|
|
5311
5317
|
ATTESTATIONS: () => ATTESTATIONS,
|
|
5312
5318
|
AUTH_MODES: () => AUTH_MODES,
|
|
5313
5319
|
DATA_HANDLING_MODES: () => DATA_HANDLING_MODES,
|
|
5320
|
+
DESCRIPTION_MIN: () => DESCRIPTION_MIN,
|
|
5314
5321
|
PLATFORMS: () => PLATFORMS,
|
|
5315
5322
|
attestationId: () => attestationId,
|
|
5316
5323
|
blockerLines: () => blockerLines,
|
|
@@ -5445,7 +5452,7 @@ function printReport(r, json) {
|
|
|
5445
5452
|
process.stdout.write(JSON.stringify(r) + "\n");
|
|
5446
5453
|
return;
|
|
5447
5454
|
}
|
|
5448
|
-
console.log(r.rendered.text);
|
|
5455
|
+
console.log(r.rendered.text.replace(/<project>/g, r.report.project.id));
|
|
5449
5456
|
if (r.probe_note) console.log(import_chalk17.default.dim(r.probe_note));
|
|
5450
5457
|
}
|
|
5451
5458
|
function blockerLines(r, platforms) {
|
|
@@ -5456,7 +5463,7 @@ function blockerLines(r, platforms) {
|
|
|
5456
5463
|
function isApiError(e, status2) {
|
|
5457
5464
|
return e instanceof ApiError && (status2 === void 0 || e.status === status2);
|
|
5458
5465
|
}
|
|
5459
|
-
var import_chalk17, PLATFORMS, ATTESTATIONS, AUTH_MODES, DATA_HANDLING_MODES, reportPath;
|
|
5466
|
+
var import_chalk17, PLATFORMS, ATTESTATIONS, AUTH_MODES, DATA_HANDLING_MODES, reportPath, DESCRIPTION_MIN;
|
|
5460
5467
|
var init_directory = __esm({
|
|
5461
5468
|
"src/lib/directory.ts"() {
|
|
5462
5469
|
"use strict";
|
|
@@ -5482,6 +5489,7 @@ var init_directory = __esm({
|
|
|
5482
5489
|
AUTH_MODES = ["signin", "open"];
|
|
5483
5490
|
DATA_HANDLING_MODES = ["own", "proxied", "third-party"];
|
|
5484
5491
|
reportPath = (proj2) => `/projects/${encodeURIComponent(proj2.projectId)}/${encodeURIComponent(proj2.apiVersion)}/directory-report`;
|
|
5492
|
+
DESCRIPTION_MIN = 300;
|
|
5485
5493
|
}
|
|
5486
5494
|
});
|
|
5487
5495
|
|
|
@@ -7186,225 +7194,6 @@ var init_agent = __esm({
|
|
|
7186
7194
|
}
|
|
7187
7195
|
});
|
|
7188
7196
|
|
|
7189
|
-
// src/lib/directory-docs.ts
|
|
7190
|
-
function directoryDocsPrompt(facts) {
|
|
7191
|
-
return [
|
|
7192
|
-
`Write the three documents a connector listing needs for "${facts.name}", from the FACTS below and nothing else.`,
|
|
7193
|
-
"Rules: plain English, second person, no marketing, no prices, no promo. Never invent a result: when you describe what a tool returns, cite only the recorded result shapes in the facts (keys, item counts, heads); if none is recorded, say what the tool is for and that the assistant will show the result.",
|
|
7194
|
-
"Never write credentials, usernames, tokens or internal hostnames. Troubleshooting cites the failure modes with their exact texts.",
|
|
7195
|
-
`The sign-in: ${facts.sign_in_shape}`,
|
|
7196
|
-
`Answer with JSON matching the schema: docs_markdown (the public page "Using <name> in Claude": purpose \xB7 how it works from Claude's side \xB7 connect and sign in \xB7 the example prompts with what to expect \xB7 troubleshooting \xB7 support), reviewer_markdown (for someone who has never seen the product: add the connector, the sign-in screen, the account, what data it holds, one prompt per tool with the recorded result shape), portal_answers (use_cases 200+ chars, prerequisites one sentence, access, 1\u20135 categories).`,
|
|
7197
|
-
"",
|
|
7198
|
-
"FACTS:",
|
|
7199
|
-
JSON.stringify(facts, null, 2)
|
|
7200
|
-
].join("\n");
|
|
7201
|
-
}
|
|
7202
|
-
function lintDocument(markdown, opts = {}) {
|
|
7203
|
-
const out = [];
|
|
7204
|
-
const text = markdown ?? "";
|
|
7205
|
-
if (!text.trim()) out.push({ code: "EMPTY", detail: "the document is empty" });
|
|
7206
|
-
if (text.length > DIRECTORY_DOC_MAX_CHARS) out.push({ code: "TOO_LONG", detail: `${text.length} characters; the limit is ${DIRECTORY_DOC_MAX_CHARS}` });
|
|
7207
|
-
const u = (opts.reviewerUsername ?? "").trim();
|
|
7208
|
-
if (u.length >= 3 && text.toLowerCase().includes(u.toLowerCase())) out.push({ code: "REVIEWER_USERNAME", detail: "the reviewer username appears; it belongs in the packet only" });
|
|
7209
|
-
if (BEARER_LIKE.test(text)) out.push({ code: "BEARER_LIKE", detail: "a bearer-like or JWT-shaped string appears" });
|
|
7210
|
-
if (KEY_LIKE.test(text)) out.push({ code: "KEY_LIKE", detail: "a key-like string (sk_/pk_/\u2026) appears" });
|
|
7211
|
-
if (PASSWORD_LINE.test(text)) out.push({ code: "PASSWORD_LINE", detail: "a password-like line appears" });
|
|
7212
|
-
if (opts.public && INTERNAL_HOST.test(text)) out.push({ code: "INTERNAL_HOST", detail: "an upstream or platform-internal host appears" });
|
|
7213
|
-
return out;
|
|
7214
|
-
}
|
|
7215
|
-
function parseGeneratedDocs(stdout) {
|
|
7216
|
-
const tryObj = (v) => {
|
|
7217
|
-
if (!v || typeof v !== "object") return null;
|
|
7218
|
-
const o = v;
|
|
7219
|
-
const pa = o.portal_answers;
|
|
7220
|
-
if (typeof o.docs_markdown !== "string" || typeof o.reviewer_markdown !== "string" || !pa || typeof pa !== "object") return null;
|
|
7221
|
-
return { docs_markdown: o.docs_markdown, reviewer_markdown: o.reviewer_markdown, portal_answers: { use_cases: String(pa.use_cases ?? ""), prerequisites: String(pa.prerequisites ?? ""), access: ["read", "write", "both"].includes(String(pa.access)) ? String(pa.access) : "both", categories: Array.isArray(pa.categories) ? pa.categories.map(String).slice(0, 5) : [] } };
|
|
7222
|
-
};
|
|
7223
|
-
let parsed = null;
|
|
7224
|
-
try {
|
|
7225
|
-
parsed = JSON.parse(stdout.trim());
|
|
7226
|
-
} catch {
|
|
7227
|
-
parsed = null;
|
|
7228
|
-
}
|
|
7229
|
-
if (parsed && typeof parsed === "object") {
|
|
7230
|
-
const o = parsed;
|
|
7231
|
-
const direct = tryObj(o);
|
|
7232
|
-
if (direct) return direct;
|
|
7233
|
-
const so = tryObj(o.structured_output);
|
|
7234
|
-
if (so) return so;
|
|
7235
|
-
if (typeof o.result === "string") {
|
|
7236
|
-
try {
|
|
7237
|
-
const r = tryObj(JSON.parse(o.result));
|
|
7238
|
-
if (r) return r;
|
|
7239
|
-
} catch {
|
|
7240
|
-
}
|
|
7241
|
-
}
|
|
7242
|
-
}
|
|
7243
|
-
for (const line of stdout.split("\n").reverse()) {
|
|
7244
|
-
const s = line.trim();
|
|
7245
|
-
if (!s.startsWith("{")) continue;
|
|
7246
|
-
try {
|
|
7247
|
-
const ev = JSON.parse(s);
|
|
7248
|
-
const so = tryObj(ev.structured_output);
|
|
7249
|
-
if (so) return so;
|
|
7250
|
-
if (typeof ev.result === "string") {
|
|
7251
|
-
const r = tryObj(JSON.parse(ev.result));
|
|
7252
|
-
if (r) return r;
|
|
7253
|
-
}
|
|
7254
|
-
} catch {
|
|
7255
|
-
}
|
|
7256
|
-
}
|
|
7257
|
-
return null;
|
|
7258
|
-
}
|
|
7259
|
-
var DIRECTORY_DOCS_JSON_SCHEMA, DIRECTORY_DOC_MAX_CHARS, BEARER_LIKE, KEY_LIKE, PASSWORD_LINE, INTERNAL_HOST;
|
|
7260
|
-
var init_directory_docs = __esm({
|
|
7261
|
-
"src/lib/directory-docs.ts"() {
|
|
7262
|
-
"use strict";
|
|
7263
|
-
DIRECTORY_DOCS_JSON_SCHEMA = {
|
|
7264
|
-
type: "object",
|
|
7265
|
-
additionalProperties: false,
|
|
7266
|
-
required: ["docs_markdown", "reviewer_markdown", "portal_answers"],
|
|
7267
|
-
properties: {
|
|
7268
|
-
docs_markdown: { type: "string", description: `The public documentation page, Markdown: "Using <name> in Claude" \u2014 purpose, how it works from Claude's side, connect and sign in, the three prompts with what to expect, troubleshooting (the known failure modes with their exact texts), support.` },
|
|
7269
|
-
reviewer_markdown: { type: "string", description: "Reviewer instructions for someone who has never seen the product: add the connector, the sign-in screen, the account, what data it holds, one prompt per tool with the recorded result shape. No credentials." },
|
|
7270
|
-
portal_answers: {
|
|
7271
|
-
type: "object",
|
|
7272
|
-
additionalProperties: false,
|
|
7273
|
-
required: ["use_cases", "prerequisites", "access", "categories"],
|
|
7274
|
-
properties: {
|
|
7275
|
-
use_cases: { type: "string", description: "What a person asks it, in their words; 200+ characters." },
|
|
7276
|
-
prerequisites: { type: "string", description: "What a user needs before it works, one sentence." },
|
|
7277
|
-
access: { type: "string", enum: ["read", "write", "both"] },
|
|
7278
|
-
categories: { type: "array", items: { type: "string" }, minItems: 1, maxItems: 5, description: "Free-text category suggestions; the producer picks the closest in the portal." }
|
|
7279
|
-
}
|
|
7280
|
-
}
|
|
7281
|
-
}
|
|
7282
|
-
};
|
|
7283
|
-
DIRECTORY_DOC_MAX_CHARS = 64e3;
|
|
7284
|
-
BEARER_LIKE = /(?:bearer\s+[A-Za-z0-9._~+/=-]{8,}|\b[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b)/i;
|
|
7285
|
-
KEY_LIKE = /\b(?:sk|pk|rk|key|tok|abz)_[A-Za-z0-9_-]{8,}\b/i;
|
|
7286
|
-
PASSWORD_LINE = /^\s*(?:[-*]\s*)?(?:\*\*)?(?:password|passwd|pwd|secret)(?:\*\*)?\s*[:=]\s*\S+/im;
|
|
7287
|
-
INTERNAL_HOST = /\b[a-z0-9.-]+\.(abz\.run|tryabz\.run|workers\.dev)\b/i;
|
|
7288
|
-
}
|
|
7289
|
-
});
|
|
7290
|
-
|
|
7291
|
-
// src/commands/connector-docs.ts
|
|
7292
|
-
var connector_docs_exports = {};
|
|
7293
|
-
__export(connector_docs_exports, {
|
|
7294
|
-
askDocsQuestion: () => askDocsQuestion,
|
|
7295
|
-
generateWithClaude: () => generateWithClaude,
|
|
7296
|
-
runConnectorDocs: () => runConnectorDocs,
|
|
7297
|
-
storeGeneratedDocs: () => storeGeneratedDocs,
|
|
7298
|
-
verifyDocsUrl: () => verifyDocsUrl
|
|
7299
|
-
});
|
|
7300
|
-
function generateWithClaude(prompt, deps = {}) {
|
|
7301
|
-
const spawn5 = deps.spawn ?? import_child_process5.spawnSync;
|
|
7302
|
-
const res = spawn5("claude", ["-p", prompt, "--output-format", "json", "--json-schema", JSON.stringify(DIRECTORY_DOCS_JSON_SCHEMA), "--tools", "", "--max-budget-usd", "2", "--permission-mode", "dontAsk"], { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], timeout: 24e4, env: { ...process.env, CLAUDECODE: "" } });
|
|
7303
|
-
const docs = parseGeneratedDocs(String(res.stdout ?? ""));
|
|
7304
|
-
return { docs, detail: docs ? "generated on Claude Code" : `Claude Code answered without the three documents${res.status != null && res.status !== 0 ? ` (exit ${res.status})` : ""}` };
|
|
7305
|
-
}
|
|
7306
|
-
async function storeGeneratedDocs(caller, proj2, docs, o) {
|
|
7307
|
-
const bad = [...lintDocument(docs.docs_markdown, { reviewerUsername: o.reviewerUsername, public: true }).map((f2) => `docs: ${f2.code}`), ...lintDocument(docs.reviewer_markdown, { reviewerUsername: o.reviewerUsername }).map((f2) => `reviewer: ${f2.code}`)];
|
|
7308
|
-
if (bad.length) throw new Error(`The generated documents were refused by the lint (${bad.join(", ")}); nothing was stored. Run again, or write them by hand on the dashboard page.`);
|
|
7309
|
-
const out = await producer(caller, { method: "PUT", path: `${projectPath(proj2)}/directory-documents`, body: { docs_markdown: docs.docs_markdown, reviewer_markdown: docs.reviewer_markdown, portal_answers: docs.portal_answers, source: "generated", facts_hash: o.factsHash, use_generated_docs_url: o.useGeneratedDocsUrl }, summary: "Store the generated documents" });
|
|
7310
|
-
return out.changed ?? [];
|
|
7311
|
-
}
|
|
7312
|
-
async function verifyDocsUrl(url, serverName, f2 = fetch) {
|
|
7313
|
-
if (!/^https:\/\/\S+$/i.test(url)) return { ok: false, reason: "not an https URL" };
|
|
7314
|
-
try {
|
|
7315
|
-
const r = await f2(url, { redirect: "follow", signal: AbortSignal.timeout(8e3), headers: { accept: "text/html, */*;q=0.5", "user-agent": "apiblaze-cli directory-check" } });
|
|
7316
|
-
if (r.status !== 200) return { ok: false, reason: `answered ${r.status}` };
|
|
7317
|
-
const body = (await r.text().catch(() => "")).toLowerCase();
|
|
7318
|
-
if (serverName && !body.includes(serverName.toLowerCase())) return { ok: false, reason: `the page does not mention "${serverName}"` };
|
|
7319
|
-
return { ok: true, reason: "live" };
|
|
7320
|
-
} catch (e) {
|
|
7321
|
-
return { ok: false, reason: e instanceof Error ? e.message : "unreachable" };
|
|
7322
|
-
}
|
|
7323
|
-
}
|
|
7324
|
-
async function runConnectorDocs(project, opts = {}) {
|
|
7325
|
-
const caller = await requireCaller();
|
|
7326
|
-
const { teamId } = await resolveActingTeam(caller, opts.team);
|
|
7327
|
-
const proj2 = await resolveProjectVia(caller, teamId, project, opts.apiversion);
|
|
7328
|
-
const json = !!opts.json;
|
|
7329
|
-
const say = (s) => {
|
|
7330
|
-
if (!json) console.log(s);
|
|
7331
|
-
};
|
|
7332
|
-
const deps = opts.deps ?? {};
|
|
7333
|
-
const fr = await producer(caller, { method: "GET", path: `${projectPath(proj2)}/directory-facts?view=packet`, summary: "The facts the generator reads" });
|
|
7334
|
-
const prompt = directoryDocsPrompt(fr.facts);
|
|
7335
|
-
let docs = null;
|
|
7336
|
-
let how = "";
|
|
7337
|
-
if (opts.from) {
|
|
7338
|
-
docs = parseGeneratedDocs(import_fs5.default.readFileSync(opts.from, "utf8"));
|
|
7339
|
-
if (!docs) throw new Error(`${opts.from} does not hold the three documents (docs_markdown, reviewer_markdown, portal_answers).`);
|
|
7340
|
-
how = `from ${opts.from}`;
|
|
7341
|
-
} else if (opts.promptOut) {
|
|
7342
|
-
import_fs5.default.writeFileSync(opts.promptOut, prompt + "\n", { mode: 384 });
|
|
7343
|
-
say(import_chalk26.default.green(` \u2713 prompt written to ${opts.promptOut}`));
|
|
7344
|
-
say(import_chalk26.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>`));
|
|
7345
|
-
return;
|
|
7346
|
-
} else {
|
|
7347
|
-
const installed3 = deps.claudeInstalled ?? detectExternalClis().some((c) => c.kind === "claude");
|
|
7348
|
-
if (!installed3) {
|
|
7349
|
-
const file2 = import_path4.default.join(import_os2.default.tmpdir(), `apiblaze-connector-docs-${proj2.projectId}.txt`);
|
|
7350
|
-
import_fs5.default.writeFileSync(file2, prompt + "\n", { mode: 384 });
|
|
7351
|
-
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.`);
|
|
7352
|
-
}
|
|
7353
|
-
say(import_chalk26.default.dim(" generating the three documents on Claude Code (30\u201390 s)\u2026"));
|
|
7354
|
-
const g = generateWithClaude(prompt, deps);
|
|
7355
|
-
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.`);
|
|
7356
|
-
docs = g.docs;
|
|
7357
|
-
how = g.detail;
|
|
7358
|
-
}
|
|
7359
|
-
const changed = await storeGeneratedDocs(caller, proj2, docs, { factsHash: fr.facts_hash, reviewerUsername: fr.facts.reviewer_username ?? null, useGeneratedDocsUrl: true });
|
|
7360
|
-
if (json) {
|
|
7361
|
-
process.stdout.write(JSON.stringify({ project: proj2.projectId, changed, docs_url: fr.generated_docs_url, how }) + "\n");
|
|
7362
|
-
return;
|
|
7363
|
-
}
|
|
7364
|
-
console.log(import_chalk26.default.green(` \u2713 documents stored (${how}): ${changed.join(", ")}`));
|
|
7365
|
-
console.log(` Documentation page: ${import_chalk26.default.cyan(fr.generated_docs_url)} \u2014 link it from your site; edit it on the dashboard any time.`);
|
|
7366
|
-
}
|
|
7367
|
-
async function askDocsQuestion(caller, proj2, state, interactive2, say, deps = {}) {
|
|
7368
|
-
if (!interactive2) return false;
|
|
7369
|
-
const { default: inquirer3 } = await import("inquirer");
|
|
7370
|
-
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" }]);
|
|
7371
|
-
const v = a.value.trim();
|
|
7372
|
-
if (!v || /^n(o)?$/i.test(v)) {
|
|
7373
|
-
say(import_chalk26.default.dim(" using the generated page"));
|
|
7374
|
-
try {
|
|
7375
|
-
await runConnectorDocs(proj2.projectName, { deps, yes: true });
|
|
7376
|
-
} catch (e) {
|
|
7377
|
-
say(import_chalk26.default.yellow(` ${e instanceof Error ? e.message : String(e)}`));
|
|
7378
|
-
await producer(caller, { method: "PUT", path: `${projectPath(proj2)}/directory-documents`, body: { use_generated_docs_url: true }, summary: "Point docs at the generated page" });
|
|
7379
|
-
}
|
|
7380
|
-
return true;
|
|
7381
|
-
}
|
|
7382
|
-
const check = await verifyDocsUrl(v, state.serverName, deps.fetch);
|
|
7383
|
-
if (!check.ok) {
|
|
7384
|
-
say(import_chalk26.default.yellow(` ${v} is not usable as documentation yet: ${check.reason}. Not stored.`));
|
|
7385
|
-
return false;
|
|
7386
|
-
}
|
|
7387
|
-
await producer(caller, { method: "PATCH", path: projectPath(proj2), body: { directory_metadata: { docs_url: v, docs_confirmed: true } }, summary: "Store the documentation URL" });
|
|
7388
|
-
say(import_chalk26.default.green(` \u2713 docs: ${v} (live, names ${state.serverName ?? "the server"})`));
|
|
7389
|
-
return true;
|
|
7390
|
-
}
|
|
7391
|
-
var import_chalk26, import_fs5, import_os2, import_path4, import_child_process5, projectPath;
|
|
7392
|
-
var init_connector_docs = __esm({
|
|
7393
|
-
"src/commands/connector-docs.ts"() {
|
|
7394
|
-
"use strict";
|
|
7395
|
-
import_chalk26 = __toESM(require("chalk"));
|
|
7396
|
-
import_fs5 = __toESM(require("fs"));
|
|
7397
|
-
import_os2 = __toESM(require("os"));
|
|
7398
|
-
import_path4 = __toESM(require("path"));
|
|
7399
|
-
import_child_process5 = require("child_process");
|
|
7400
|
-
init_caller();
|
|
7401
|
-
init_directory_docs();
|
|
7402
|
-
init_external_mcp();
|
|
7403
|
-
init_brand();
|
|
7404
|
-
projectPath = (p) => `/projects/${encodeURIComponent(p.projectId)}/${encodeURIComponent(p.apiVersion)}`;
|
|
7405
|
-
}
|
|
7406
|
-
});
|
|
7407
|
-
|
|
7408
7197
|
// src/lib/headless-login.ts
|
|
7409
7198
|
async function discover(mcpUrl2, deps = {}) {
|
|
7410
7199
|
const out = { mcpUrl: mcpUrl2, unauthStatus: 0, prmUrl: null, prm: null, asUrl: null, as: null };
|
|
@@ -7579,7 +7368,8 @@ function orderTools(tools) {
|
|
|
7579
7368
|
const rank = { read: 0, create: 1, update: 2, write: 3, delete: 4 };
|
|
7580
7369
|
return [...tools].sort((a, b) => rank[toolRole(a)] - rank[toolRole(b)] || a.name.localeCompare(b.name));
|
|
7581
7370
|
}
|
|
7582
|
-
function exampleValue(name, p, known = {}) {
|
|
7371
|
+
function exampleValue(name, p, known = {}, given = {}) {
|
|
7372
|
+
if (given[name] !== void 0) return given[name];
|
|
7583
7373
|
if (p.example !== void 0 && p.example !== null) return p.example;
|
|
7584
7374
|
if (known[name] !== void 0) return known[name];
|
|
7585
7375
|
if (Array.isArray(p.enum) && p.enum.length) return p.enum[0];
|
|
@@ -7598,22 +7388,34 @@ function exampleValue(name, p, known = {}) {
|
|
|
7598
7388
|
if (/(^|_)(q|query|search|term|text|name|title)$/i.test(name)) return "test";
|
|
7599
7389
|
return "example";
|
|
7600
7390
|
}
|
|
7601
|
-
function validArgs(t, known = {}) {
|
|
7391
|
+
function validArgs(t, known = {}, given = {}) {
|
|
7602
7392
|
const out = {};
|
|
7603
7393
|
const props = t.inputProperties ?? {};
|
|
7604
7394
|
const pathParams = new Set([...(t.path ?? "").matchAll(/\{([^}]+)\}/g)].map((m) => m[1]));
|
|
7605
7395
|
for (const [name, p] of Object.entries(props)) {
|
|
7606
|
-
const wanted = p.required || pathParams.has(name) || p.example !== void 0 && p.example !== null || known[name] !== void 0;
|
|
7396
|
+
const wanted = p.required || pathParams.has(name) || p.example !== void 0 && p.example !== null || known[name] !== void 0 || given[name] !== void 0;
|
|
7607
7397
|
if (!wanted) continue;
|
|
7608
|
-
out[name] = exampleValue(name, p, known);
|
|
7398
|
+
out[name] = exampleValue(name, p, known, given);
|
|
7609
7399
|
}
|
|
7610
7400
|
return out;
|
|
7611
7401
|
}
|
|
7612
|
-
function
|
|
7402
|
+
function candidateFromPrompts(name, p, prompts) {
|
|
7403
|
+
const fmt = (p.format ?? "").toLowerCase();
|
|
7404
|
+
const text = prompts.join("\n");
|
|
7405
|
+
if (fmt === "uri" || fmt === "url" || URLISH.test(name)) {
|
|
7406
|
+
const m = text.match(/https?:\/\/[^\s"'<>)]+/);
|
|
7407
|
+
if (m) return m[0].replace(/[.,;:!?]+$/, "");
|
|
7408
|
+
const bare = text.match(/\b[a-z0-9-]+(\.[a-z0-9-]+)*\.[a-z]{2,}(\/[^\s"'<>)]*)?/i);
|
|
7409
|
+
return bare ? bare[0].replace(/[.,;:!?]+$/, "") : null;
|
|
7410
|
+
}
|
|
7411
|
+
const quoted = text.match(/["“']([^"”']{1,80})["”']/);
|
|
7412
|
+
return quoted ? quoted[1].trim() : null;
|
|
7413
|
+
}
|
|
7414
|
+
function invalidArgs(t, known = {}, given = {}) {
|
|
7613
7415
|
const props = t.inputProperties ?? {};
|
|
7614
7416
|
const names = Object.keys(props);
|
|
7615
7417
|
if (!names.length) return null;
|
|
7616
|
-
const base2 = validArgs(t, known);
|
|
7418
|
+
const base2 = validArgs(t, known, given);
|
|
7617
7419
|
const withEnum = names.find((n) => Array.isArray(props[n].enum) && props[n].enum.length);
|
|
7618
7420
|
if (withEnum) return { args: { ...base2, [withEnum]: "not-a-valid-choice" }, broke: `${withEnum}: out of enum` };
|
|
7619
7421
|
const typed = names.find((n) => ["integer", "number", "boolean"].includes((props[n].type ?? "").toLowerCase()));
|
|
@@ -7656,7 +7458,7 @@ function localOutcome(r) {
|
|
|
7656
7458
|
if (b && typeof b === "object" && b.error) return { outcome: "no_call", text: typeof b.error.message === "string" ? b.error.message : "JSON-RPC error" };
|
|
7657
7459
|
const result = b && typeof b === "object" ? b.result : null;
|
|
7658
7460
|
if (result && typeof result === "object") {
|
|
7659
|
-
const text = Array.isArray(result.content) ? result.content.filter((c) => c && c.type === "text").map((c) => String(c.text)).join(" ").slice(0,
|
|
7461
|
+
const text = Array.isArray(result.content) ? result.content.filter((c) => c && c.type === "text").map((c) => String(c.text)).join(" ").slice(0, 65536) : "";
|
|
7660
7462
|
return result.isError ? { outcome: "error", text } : { outcome: "ok", text };
|
|
7661
7463
|
}
|
|
7662
7464
|
return { outcome: "no_call", text: `HTTP ${r.status}` };
|
|
@@ -7684,8 +7486,9 @@ function claudeRecipe(o) {
|
|
|
7684
7486
|
""
|
|
7685
7487
|
];
|
|
7686
7488
|
}
|
|
7687
|
-
function mcpConfigDocument(url, bearer) {
|
|
7688
|
-
|
|
7489
|
+
function mcpConfigDocument(url, bearer, runId2) {
|
|
7490
|
+
const headers = { ...bearer ? { Authorization: `Bearer ${bearer}` } : {}, ...runId2 ? { "apiblaze-directory-run": runId2 } : {} };
|
|
7491
|
+
return JSON.stringify({ mcpServers: { abz: { type: "http", url, ...Object.keys(headers).length ? { headers } : {} } } }, null, 2) + "\n";
|
|
7689
7492
|
}
|
|
7690
7493
|
function toolCallsInClaudeOutput(stdout) {
|
|
7691
7494
|
const names = /* @__PURE__ */ new Set();
|
|
@@ -7707,21 +7510,143 @@ function toolCallsInClaudeOutput(stdout) {
|
|
|
7707
7510
|
function redactHead(text, max = 200) {
|
|
7708
7511
|
return text.replace(SECRETISH, "[redacted]").slice(0, max);
|
|
7709
7512
|
}
|
|
7710
|
-
var ID_PARAM, SECRETISH;
|
|
7513
|
+
var ID_PARAM, URLISH, SECRETISH;
|
|
7711
7514
|
var init_exercise = __esm({
|
|
7712
7515
|
"src/lib/exercise.ts"() {
|
|
7713
7516
|
"use strict";
|
|
7714
7517
|
ID_PARAM = /(^|_)(id|uuid|key|slug|number|no)$/i;
|
|
7518
|
+
URLISH = /(^|_)(url|uri|link|website|site|homepage|repo|repository)$/i;
|
|
7715
7519
|
SECRETISH = /(bearer\s+[A-Za-z0-9._~+/=-]{8,}|[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}|\b(?:sk|pk|rk|key|tok|abz)_[A-Za-z0-9_-]{8,})/gi;
|
|
7716
7520
|
}
|
|
7717
7521
|
});
|
|
7718
7522
|
|
|
7523
|
+
// src/lib/portal-drafts.ts
|
|
7524
|
+
var portal_drafts_exports = {};
|
|
7525
|
+
__export(portal_drafts_exports, {
|
|
7526
|
+
ANSWER_MIN: () => ANSWER_MIN,
|
|
7527
|
+
describeResult: () => describeResult,
|
|
7528
|
+
draftDescription: () => draftDescription,
|
|
7529
|
+
draftMissing: () => draftMissing,
|
|
7530
|
+
draftReviewerInstructions: () => draftReviewerInstructions,
|
|
7531
|
+
draftUseCases: () => draftUseCases
|
|
7532
|
+
});
|
|
7533
|
+
function describeResult(text) {
|
|
7534
|
+
const t = (text ?? "").trim();
|
|
7535
|
+
if (!t) return "an empty answer";
|
|
7536
|
+
try {
|
|
7537
|
+
const v = JSON.parse(t);
|
|
7538
|
+
if (Array.isArray(v)) return v.length ? `a list of ${v.length} item${v.length === 1 ? "" : "s"}${v[0] && typeof v[0] === "object" ? ` with ${Object.keys(v[0]).slice(0, 8).join(", ")}` : ""}` : "an empty list";
|
|
7539
|
+
if (v && typeof v === "object") {
|
|
7540
|
+
const keys = Object.keys(v);
|
|
7541
|
+
return keys.length ? `a JSON answer with ${keys.slice(0, 8).join(", ")}${keys.length > 8 ? ` (+${keys.length - 8} more)` : ""}` : "an empty object";
|
|
7542
|
+
}
|
|
7543
|
+
} catch {
|
|
7544
|
+
}
|
|
7545
|
+
const head = t.replace(/\s+/g, " ").slice(0, 80);
|
|
7546
|
+
return `a text answer ("${head}${t.length > 80 ? "\u2026" : ""}")`;
|
|
7547
|
+
}
|
|
7548
|
+
function argsText(args) {
|
|
7549
|
+
if (!args || !Object.keys(args).length) return "no arguments";
|
|
7550
|
+
return Object.entries(args).map(([k, v]) => `${k}=${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
|
|
7551
|
+
}
|
|
7552
|
+
function toolsCalledBy(i, prompt) {
|
|
7553
|
+
const names = i.promptTools[prompt] ?? [];
|
|
7554
|
+
return names.map((n) => i.tools.find((t) => t.name === n) ?? { name: n });
|
|
7555
|
+
}
|
|
7556
|
+
function draftUseCases(i) {
|
|
7557
|
+
const parts = [];
|
|
7558
|
+
const working = i.prompts.filter((p) => (i.promptTools[p] ?? []).length);
|
|
7559
|
+
const list = working.length ? working : i.prompts;
|
|
7560
|
+
if (list.length) parts.push(`People ask it, in their own words: ${list.map((p) => `"${p}"`).join(" \xB7 ")}.`);
|
|
7561
|
+
const groups = /* @__PURE__ */ new Map();
|
|
7562
|
+
for (const p of list) {
|
|
7563
|
+
const called = toolsCalledBy(i, p);
|
|
7564
|
+
if (!called.length) continue;
|
|
7565
|
+
const key = called.map((t) => t.name).join("+");
|
|
7566
|
+
if (!groups.has(key)) groups.set(key, { tools: called, prompts: [] });
|
|
7567
|
+
groups.get(key).prompts.push(p);
|
|
7568
|
+
}
|
|
7569
|
+
const short = (p) => `"${p.length > 50 ? `${p.slice(0, 47)}\u2026` : p}"`;
|
|
7570
|
+
for (const g of groups.values()) {
|
|
7571
|
+
const row = i.rows.find((r) => r.tool === g.tools[0].name && r.valid === "ok");
|
|
7572
|
+
const which = g.prompts.length === list.length && list.length > 1 ? "Each of these" : g.prompts.map(short).join(", ");
|
|
7573
|
+
parts.push(`${which} make${g.prompts.length === 1 ? "s" : ""} Claude call ${g.tools.map(titleOf).join(" and ")}${row ? `, which returns ${shapeOf(row)}` : ""}.`);
|
|
7574
|
+
}
|
|
7575
|
+
const uncalled = i.tools.filter((t) => !Object.values(i.promptTools).some((ns) => ns.includes(t.name)));
|
|
7576
|
+
if (uncalled.length) parts.push(`Also available: ${uncalled.map((t) => `${titleOf(t)}${(t.description ?? "").trim() ? ` (${(t.description ?? "").trim().replace(/\.$/, "")})` : ""}`).join("; ")}.`);
|
|
7577
|
+
parts.push(readOnly(i) ? "Every tool only reads; nothing is created or changed." : "Write tools change data in the signed-in account only.");
|
|
7578
|
+
return parts.join(" ");
|
|
7579
|
+
}
|
|
7580
|
+
function draftReviewerInstructions(i) {
|
|
7581
|
+
const lines = [];
|
|
7582
|
+
lines.push(`Add ${i.publishedUrl} as a custom connector in Claude (Settings \u2192 Connectors \u2192 Add custom connector).`);
|
|
7583
|
+
lines.push(i.open ? "No sign-in: the data is public and every tool is read-only. Connect and ask." : `Sign in with the reviewer account in this packet${i.reviewerUsername ? ` (username ${i.reviewerUsername}; the password is in the packet, never in this text)` : ""}.`);
|
|
7584
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7585
|
+
for (const p of i.prompts) {
|
|
7586
|
+
const called = toolsCalledBy(i, p);
|
|
7587
|
+
if (!called.length) continue;
|
|
7588
|
+
const row = i.rows.find((r) => r.tool === called[0].name && r.valid === "ok");
|
|
7589
|
+
called.forEach((t) => seen.add(t.name));
|
|
7590
|
+
lines.push(`Ask: "${p}" \u2014 Claude calls ${called.map(titleOf).join(" and ")}${row ? `; expect ${shapeOf(row)}` : ""}.`);
|
|
7591
|
+
}
|
|
7592
|
+
for (const r of i.rows) {
|
|
7593
|
+
if (seen.has(r.tool) || r.valid !== "ok") continue;
|
|
7594
|
+
const t = i.tools.find((x) => x.name === r.tool) ?? { name: r.tool };
|
|
7595
|
+
lines.push(`Ask Claude to ${titleOf(t).replace(/^./, (c) => c.toLowerCase())} (${argsText(r.args)}); expect ${shapeOf(r)}.`);
|
|
7596
|
+
}
|
|
7597
|
+
const errs = i.rows.filter((r) => r.invalid === "error" && r.invalidText);
|
|
7598
|
+
for (const r of errs.slice(0, 2)) {
|
|
7599
|
+
const [broke, ...rest] = r.invalidText.split(" \u2192 ");
|
|
7600
|
+
const answer = rest.join(" \u2192 ").replace(/\s+/g, " ").slice(0, 100);
|
|
7601
|
+
lines.push(`A bad argument (${r.tool}, ${broke}) answers "${answer}" \u2014 a message, not a crash.`);
|
|
7602
|
+
}
|
|
7603
|
+
const declined = i.rows.filter((r) => /declined|not exercised/i.test(r.note ?? ""));
|
|
7604
|
+
if (declined.length) lines.push(`Not run in the test, by choice: ${declined.map((r) => r.tool).join(", ")} (write tools; they change data in the signed-in account only).`);
|
|
7605
|
+
lines.push(readOnly(i) ? "Every tool only reads; nothing is created or changed." : "Every write in the test was on data the test itself created.");
|
|
7606
|
+
return lines.map((l, n) => `${n + 1}. ${l}`).join("\n");
|
|
7607
|
+
}
|
|
7608
|
+
function draftDescription(i) {
|
|
7609
|
+
const parts = [];
|
|
7610
|
+
const base2 = (i.description ?? "").trim() || (i.tagline ?? "").trim();
|
|
7611
|
+
if (base2) parts.push(base2.replace(/\.?$/, "."));
|
|
7612
|
+
const toolLines = i.tools.map((t) => {
|
|
7613
|
+
const d = (t.description ?? "").trim().replace(/\.$/, "");
|
|
7614
|
+
return d && d.toLowerCase() !== titleOf(t).toLowerCase() ? `${titleOf(t)}: ${d}` : titleOf(t);
|
|
7615
|
+
});
|
|
7616
|
+
if (toolLines.length) parts.push(`${i.tools.length === 1 ? "One tool" : `${i.tools.length} tools`}: ${toolLines.join("; ")}.`);
|
|
7617
|
+
const working = i.prompts.filter((p) => (i.promptTools[p] ?? []).length);
|
|
7618
|
+
if (working.length) parts.push(`Ask it things like ${working.map((p) => `"${p}"`).join(", ")}.`);
|
|
7619
|
+
const ok = i.rows.find((r) => r.valid === "ok");
|
|
7620
|
+
if (ok) parts.push(`What comes back is ${shapeOf(ok)}, which Claude reads and explains.`);
|
|
7621
|
+
parts.push(readOnly(i) ? "Every tool only reads; nothing is created or changed." : "Writes change data in the signed-in account only.");
|
|
7622
|
+
return parts.join(" ");
|
|
7623
|
+
}
|
|
7624
|
+
function draftMissing(i, current) {
|
|
7625
|
+
const out = [];
|
|
7626
|
+
const len = (v) => (v ?? "").trim().length;
|
|
7627
|
+
if (len(current.description) < ANSWER_MIN.description) out.push({ key: "description", label: "Listing description", draft: draftDescription({ ...i, description: current.description ?? i.description }), current: (current.description ?? "").trim() });
|
|
7628
|
+
if (len(current.use_cases) < ANSWER_MIN.use_cases) out.push({ key: "use_cases", label: "Use cases", draft: draftUseCases(i), current: (current.use_cases ?? "").trim() });
|
|
7629
|
+
if (len(current.reviewer_instructions) < ANSWER_MIN.reviewer_instructions) out.push({ key: "reviewer_instructions", label: "Reviewer instructions", draft: draftReviewerInstructions(i), current: (current.reviewer_instructions ?? "").trim() });
|
|
7630
|
+
return out;
|
|
7631
|
+
}
|
|
7632
|
+
var ANSWER_MIN, titleOf, shapeOf, readOnly;
|
|
7633
|
+
var init_portal_drafts = __esm({
|
|
7634
|
+
"src/lib/portal-drafts.ts"() {
|
|
7635
|
+
"use strict";
|
|
7636
|
+
ANSWER_MIN = { use_cases: 200, description: 300, reviewer_instructions: 400 };
|
|
7637
|
+
titleOf = (t) => (t.title ?? "").trim() || t.name;
|
|
7638
|
+
shapeOf = (row) => row ? row.resultShape ?? describeResult(row.validText) : "";
|
|
7639
|
+
readOnly = (i) => i.tools.every((t) => i.rows.find((r) => r.tool === t.name)?.role === "read" || /^(get|head)$/i.test(t.method ?? "get"));
|
|
7640
|
+
}
|
|
7641
|
+
});
|
|
7642
|
+
|
|
7719
7643
|
// src/commands/connector-exercise.ts
|
|
7720
7644
|
var connector_exercise_exports = {};
|
|
7721
7645
|
__export(connector_exercise_exports, {
|
|
7722
7646
|
DASHBOARD_BASE: () => DASHBOARD_BASE8,
|
|
7723
7647
|
RUN_HEADER: () => RUN_HEADER,
|
|
7724
7648
|
RUN_META_KEY: () => RUN_META_KEY,
|
|
7649
|
+
createPromptTester: () => createPromptTester,
|
|
7725
7650
|
reportLink: () => reportLink,
|
|
7726
7651
|
runConnectorAnswers: () => runConnectorAnswers,
|
|
7727
7652
|
runConnectorExercise: () => runConnectorExercise
|
|
@@ -7745,10 +7670,10 @@ async function runConnectorExercise(project, opts = {}) {
|
|
|
7745
7670
|
if (!json) console.log(s);
|
|
7746
7671
|
};
|
|
7747
7672
|
const deps = opts.deps ?? {};
|
|
7748
|
-
say(
|
|
7673
|
+
if (opts.report !== false) say(import_chalk26.default.cyan(` Report: ${reportLink(proj2.projectId, proj2.apiVersion)}`));
|
|
7749
7674
|
if (opts.emptyOk?.length) {
|
|
7750
|
-
await producer(caller, { method: "PATCH", path:
|
|
7751
|
-
say(
|
|
7675
|
+
await producer(caller, { method: "PATCH", path: projectPath(proj2), body: { directory_metadata: { empty_ok: opts.emptyOk } }, summary: "Mark tools legitimately empty" });
|
|
7676
|
+
say(import_chalk26.default.dim(` marked legitimately empty: ${opts.emptyOk.join(", ")}`));
|
|
7752
7677
|
}
|
|
7753
7678
|
let r = await fetchDirectoryReport(caller, proj2, { probe: false });
|
|
7754
7679
|
const target2 = r.target ?? {};
|
|
@@ -7758,47 +7683,74 @@ async function runConnectorExercise(project, opts = {}) {
|
|
|
7758
7683
|
if (!tools.length) throw new Error(`${proj2.projectName} exposes no tools to exercise.`);
|
|
7759
7684
|
if (target2.project?.directoryReady === false) throw new Error(`Directory-ready mode is off for ${proj2.projectName} v${proj2.apiVersion}: nothing is recorded and the sign-in door is shut. Turn it on first (\`${B.cli} connector ${proj2.projectId}\`, or the switch on the dashboard) and run again.`);
|
|
7760
7685
|
const open = target2.project?.authMode === "open";
|
|
7761
|
-
const opened = await producer(caller, { method: "POST", path: `${
|
|
7686
|
+
const opened = await producer(caller, { method: "POST", path: `${projectPath(proj2)}/directory-runs`, body: { runner: "cli-direct" }, summary: "Open an exercise run" });
|
|
7762
7687
|
const runId2 = opened.run.run_id;
|
|
7763
7688
|
const notes = [];
|
|
7764
7689
|
const rows = [];
|
|
7765
7690
|
let bearer = null;
|
|
7766
7691
|
let asMeta = null;
|
|
7767
7692
|
let configDir = null;
|
|
7693
|
+
let reviewerUsername = null;
|
|
7768
7694
|
try {
|
|
7769
7695
|
if (!open) {
|
|
7770
|
-
const packet = await producer(caller, { method: "GET", path: `${
|
|
7696
|
+
const packet = await producer(caller, { method: "GET", path: `${projectPath(proj2)}/directory-packet?probe=0&force=1`, summary: "Reviewer account (for the login)" });
|
|
7771
7697
|
const acct = packet.json?.test_account ?? null;
|
|
7772
7698
|
if (!acct) throw new Error(`No reviewer account on ${proj2.projectName}: create it first (\`${B.cli} connector reviewer ${proj2.projectId}\`), or declare open mode for public read-only data.`);
|
|
7773
7699
|
const login = await headlessPasswordLogin({ mcpUrl: publishedUrl, username: acct.username, password: acct.password }, deps);
|
|
7774
7700
|
if (!login.accessToken) throw new Error(`The reviewer could not sign in at ${login.failedAt}: ${login.detail}. Fix the sign-in and run again.`);
|
|
7775
7701
|
bearer = login.accessToken;
|
|
7776
7702
|
asMeta = login.as;
|
|
7777
|
-
|
|
7703
|
+
reviewerUsername = acct.username;
|
|
7704
|
+
say(import_chalk26.default.dim(` signed in as the reviewer (${acct.username}); the token is revoked at the end`));
|
|
7778
7705
|
} else {
|
|
7779
|
-
say(
|
|
7706
|
+
say(import_chalk26.default.dim(" open mode: no sign-in; this run's calls count as yours"));
|
|
7780
7707
|
}
|
|
7781
7708
|
const ua = `apiblaze-cli/${version} directory-exercise`;
|
|
7782
7709
|
const headers = { [RUN_HEADER]: runId2 };
|
|
7783
7710
|
const call3 = (name, args) => mcpCall(publishedUrl, "tools/call", { name, arguments: args, _meta: { [RUN_META_KEY]: runId2 } }, { bearer, headers, userAgent: ua }, deps);
|
|
7784
7711
|
const created = {};
|
|
7785
|
-
|
|
7712
|
+
const examples = { ...target2.metadata?.toolExamples ?? {} };
|
|
7713
|
+
const learned = {};
|
|
7714
|
+
const prompts = (target2.metadata?.examplePrompts ?? []).filter((p) => typeof p === "string");
|
|
7715
|
+
for (const t of opts.promptsOnly ? [] : tools) {
|
|
7786
7716
|
const role = toolRole(t);
|
|
7787
7717
|
const row = { tool: t.name, role, valid: "no_call", validText: "", invalid: "skipped", invalidText: "" };
|
|
7788
7718
|
rows.push(row);
|
|
7789
|
-
|
|
7719
|
+
let args = validArgs(t, created, examples[t.name]);
|
|
7790
7720
|
if (role !== "read") {
|
|
7791
7721
|
const go = await confirm(`${t.name} (${(t.method ?? "POST").toUpperCase()} ${t.path ?? ""}) with ${JSON.stringify(args)} \u2014 call it?`, interactive2);
|
|
7792
7722
|
if (!go) {
|
|
7793
7723
|
row.note = "not exercised: producer declined";
|
|
7794
7724
|
notes.push({ tool: t.name, note: "not exercised: producer declined" });
|
|
7795
|
-
say(` ${
|
|
7725
|
+
say(` ${import_chalk26.default.yellow("\u2013")} ${t.name} not exercised (${interactive2 ? "declined" : "non-interactive: writes need a y"})`);
|
|
7796
7726
|
continue;
|
|
7797
7727
|
}
|
|
7798
7728
|
}
|
|
7799
|
-
|
|
7729
|
+
let v = localOutcome(await call3(t.name, args));
|
|
7730
|
+
if (role === "read" && v.outcome !== "ok" && interactive2 && Object.keys(args).length) {
|
|
7731
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
7732
|
+
say(` ${mark(v.outcome)} ${t.name} refused ${JSON.stringify(args)}: ${import_chalk26.default.dim(redactHead(v.text, 120) || v.outcome)}`);
|
|
7733
|
+
const fixed = {};
|
|
7734
|
+
for (const [name, cur] of Object.entries(args)) {
|
|
7735
|
+
const p = t.inputProperties?.[name] ?? {};
|
|
7736
|
+
const proposed = candidateFromPrompts(name, p, prompts);
|
|
7737
|
+
const { value } = await inquirer3.prompt([{ type: "input", name: "value", message: `A value for ${name} that works (Enter to leave it):`, ...proposed && proposed !== String(cur) ? { default: proposed } : {} }]);
|
|
7738
|
+
const s = (value ?? "").trim();
|
|
7739
|
+
if (!s || s === String(cur)) continue;
|
|
7740
|
+
const type = (p.type ?? "string").toLowerCase();
|
|
7741
|
+
fixed[name] = type === "integer" || type === "number" ? Number.isFinite(Number(s)) ? Number(s) : s : type === "boolean" ? s === "true" : s;
|
|
7742
|
+
}
|
|
7743
|
+
if (Object.keys(fixed).length) {
|
|
7744
|
+
examples[t.name] = { ...examples[t.name] ?? {}, ...fixed };
|
|
7745
|
+
args = validArgs(t, created, examples[t.name]);
|
|
7746
|
+
v = localOutcome(await call3(t.name, args));
|
|
7747
|
+
if (v.outcome === "ok") learned[t.name] = fixed;
|
|
7748
|
+
}
|
|
7749
|
+
}
|
|
7800
7750
|
row.valid = v.outcome;
|
|
7801
7751
|
row.validText = redactHead(v.text);
|
|
7752
|
+
row.args = args;
|
|
7753
|
+
if (v.outcome === "ok") row.resultShape = describeResult(v.text);
|
|
7802
7754
|
if (v.outcome === "ok" && role === "create") {
|
|
7803
7755
|
const id = extractCreatedId(parseResultJson(v.text));
|
|
7804
7756
|
if (id) {
|
|
@@ -7808,43 +7760,52 @@ async function runConnectorExercise(project, opts = {}) {
|
|
|
7808
7760
|
}
|
|
7809
7761
|
}
|
|
7810
7762
|
if (role === "read") {
|
|
7811
|
-
const bad = invalidArgs(t, created);
|
|
7763
|
+
const bad = invalidArgs(t, created, examples[t.name]);
|
|
7812
7764
|
if (bad) {
|
|
7813
7765
|
const e = localOutcome(await call3(t.name, bad.args));
|
|
7814
7766
|
row.invalid = e.outcome;
|
|
7815
7767
|
row.invalidText = redactHead(`${bad.broke} \u2192 ${e.text}`);
|
|
7816
7768
|
}
|
|
7817
7769
|
}
|
|
7818
|
-
say(` ${mark(v.outcome)} ${t.name} ${
|
|
7770
|
+
say(` ${mark(v.outcome)} ${t.name} ${import_chalk26.default.dim(v.text.replace(/\s+/g, " ").slice(0, 80))}${row.invalid !== "skipped" ? import_chalk26.default.dim(` \xB7 bad arg: ${row.invalid === "error" ? "refused with a message" : row.invalid}`) : ""}`);
|
|
7771
|
+
}
|
|
7772
|
+
if (Object.keys(learned).length) {
|
|
7773
|
+
await producer(caller, { method: "PATCH", path: projectPath(proj2), body: { directory_metadata: { tool_examples: learned } }, summary: "Remember the working arguments" });
|
|
7774
|
+
say(import_chalk26.default.green(` \u2713 remembered the working arguments for ${Object.keys(learned).join(", ")} (used on every run from now on)`));
|
|
7819
7775
|
}
|
|
7820
7776
|
const claudeInstalled = deps.claudeInstalled ?? detectExternalClis().some((c) => c.kind === "claude");
|
|
7821
7777
|
if (!opts.noClaude && claudeInstalled) {
|
|
7822
|
-
const spawn5 = deps.spawn ??
|
|
7823
|
-
configDir =
|
|
7824
|
-
|
|
7825
|
-
const cfgPath =
|
|
7826
|
-
|
|
7827
|
-
const
|
|
7778
|
+
const spawn5 = deps.spawn ?? import_child_process5.spawnSync;
|
|
7779
|
+
configDir = import_fs5.default.mkdtempSync(import_path4.default.join(deps.tmpdir ?? import_os2.default.tmpdir(), "abz-exercise-"));
|
|
7780
|
+
import_fs5.default.chmodSync(configDir, 448);
|
|
7781
|
+
const cfgPath = import_path4.default.join(configDir, "mcp.json");
|
|
7782
|
+
import_fs5.default.writeFileSync(cfgPath, mcpConfigDocument(publishedUrl, bearer, runId2), { mode: 384 });
|
|
7783
|
+
const proven = new Map((opts.provenPromptNotes ?? []).map((n) => [n.prompt.trim(), n]));
|
|
7784
|
+
const prompts2 = (opts.promptsOnly ?? target2.metadata?.examplePrompts ?? []).filter((p) => typeof p === "string" && p.trim()).slice(0, 3);
|
|
7828
7785
|
const firstRead = tools.find((t) => toolRole(t) === "read") ?? tools[0];
|
|
7829
7786
|
const runs = [
|
|
7830
|
-
{ prompt: `Use the ${firstRead.title ?? firstRead.name} tool once and summarise what it returned in one sentence.`, allowed: [`mcp__abz__${firstRead.name}`], kind: "tool" },
|
|
7831
|
-
...
|
|
7787
|
+
...opts.promptsOnly ? [] : [{ prompt: `Use the ${firstRead.title ?? firstRead.name} tool once and summarise what it returned in one sentence.`, allowed: [`mcp__abz__${firstRead.name}`], kind: "tool" }],
|
|
7788
|
+
...prompts2.filter((p) => !proven.has(p.trim())).map((p) => ({ prompt: p, allowed: ["mcp__abz__*"], kind: "prompt" }))
|
|
7832
7789
|
];
|
|
7833
|
-
|
|
7790
|
+
for (const p of prompts2) {
|
|
7791
|
+
const n = proven.get(p.trim());
|
|
7792
|
+
if (n) notes.push({ ...n });
|
|
7793
|
+
}
|
|
7794
|
+
if (proven.size) say(import_chalk26.default.dim(` ${proven.size} prompt${proven.size > 1 ? "s" : ""} already run through Claude Code as ${proven.size > 1 ? "they were" : "it was"} typed`));
|
|
7795
|
+
say(import_chalk26.default.dim(` Claude Code pass: ${runs.length} one-shot${runs.length > 1 ? "s" : ""} (10\u201325 s each)`));
|
|
7834
7796
|
for (const x of runs) {
|
|
7835
|
-
const
|
|
7836
|
-
const calls = toolCallsInClaudeOutput(String(res.stdout ?? ""));
|
|
7797
|
+
const { calls, status: status2 } = claudeOneShot2(spawn5, cfgPath, x.prompt, x.allowed);
|
|
7837
7798
|
if (x.kind === "prompt") notes.push({ kind: "prompt", prompt: x.prompt, tool_called: calls.length > 0, tools: calls });
|
|
7838
7799
|
else notes.push({ kind: "claude-tool", tool: firstRead.name, tool_called: calls.length > 0 });
|
|
7839
|
-
say(` ${calls.length ?
|
|
7800
|
+
say(` ${calls.length ? import_chalk26.default.green("\u2713") : import_chalk26.default.yellow("\u2013")} claude: "${x.prompt.slice(0, 60)}" \u2192 ${calls.length ? `called ${calls.join(", ")}` : "no tool call"}${status2 !== 0 && status2 != null ? import_chalk26.default.dim(` (exit ${status2})`) : ""}`);
|
|
7840
7801
|
}
|
|
7841
7802
|
} else if (!opts.noClaude) {
|
|
7842
|
-
say(
|
|
7803
|
+
say(import_chalk26.default.dim(" Claude Code is not installed here: the Claude pass is skipped (TESTED_FROM_CLAUDE and EXAMPLE_PROMPTS_WORK stay open with their instruction)."));
|
|
7843
7804
|
}
|
|
7844
7805
|
} finally {
|
|
7845
7806
|
if (configDir) {
|
|
7846
7807
|
try {
|
|
7847
|
-
|
|
7808
|
+
import_fs5.default.rmSync(configDir, { recursive: true, force: true });
|
|
7848
7809
|
} catch {
|
|
7849
7810
|
}
|
|
7850
7811
|
}
|
|
@@ -7854,18 +7815,94 @@ async function runConnectorExercise(project, opts = {}) {
|
|
|
7854
7815
|
}
|
|
7855
7816
|
r = await fetchDirectoryReport(caller, proj2, { publishedUrl, ...opts.platforms ? { platforms: opts.platforms } : {} });
|
|
7856
7817
|
const verdicts = r.report.verdicts ?? null;
|
|
7857
|
-
const closed = await producer(caller, { method: "PATCH", path: `${
|
|
7818
|
+
const closed = await producer(caller, { method: "PATCH", path: `${projectPath(proj2)}/directory-runs/${encodeURIComponent(runId2)}`, body: { verdicts, scores: verdicts?.groups ?? null, notes, runner: "cli-direct", ...opts.platforms?.length ? { platforms: opts.platforms } : {}, ...opts.noEmail ? { email: false } : {} }, summary: "Close the exercise run" });
|
|
7819
|
+
const promptTools = {};
|
|
7820
|
+
for (const n of notes) if (n.kind === "prompt" && typeof n.prompt === "string") promptTools[n.prompt] = Array.isArray(n.tools) ? n.tools : [];
|
|
7821
|
+
const summary = {
|
|
7822
|
+
failedPrompts: notes.filter((n) => n.kind === "prompt" && n.tool_called === false && typeof n.prompt === "string").map((n) => n.prompt),
|
|
7823
|
+
rows,
|
|
7824
|
+
promptTools,
|
|
7825
|
+
publishedUrl,
|
|
7826
|
+
open,
|
|
7827
|
+
reviewerUsername,
|
|
7828
|
+
tools
|
|
7829
|
+
};
|
|
7858
7830
|
if (json) {
|
|
7859
7831
|
process.stdout.write(JSON.stringify({ project: proj2.projectId, run_id: runId2, link: reportLink(proj2.projectId, proj2.apiVersion), tools: rows, notes, headline: r.rendered.headline, verdicts, report: r.report }) + "\n");
|
|
7860
|
-
return;
|
|
7832
|
+
return summary;
|
|
7861
7833
|
}
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
7834
|
+
if (opts.report !== false) {
|
|
7835
|
+
console.log();
|
|
7836
|
+
printReport(r, false);
|
|
7837
|
+
console.log(import_chalk26.default.cyan(` Report: ${reportLink(proj2.projectId, proj2.apiVersion)}`));
|
|
7838
|
+
}
|
|
7839
|
+
console.log(import_chalk26.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)"));
|
|
7840
|
+
return summary;
|
|
7841
|
+
}
|
|
7842
|
+
function claudeOneShot2(spawn5, cfgPath, prompt, allowed) {
|
|
7843
|
+
const res = spawn5("claude", claudeRecipe({ prompt, mcpConfigPath: cfgPath, allowedTools: allowed }), { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], timeout: 12e4, env: { ...process.env, CLAUDECODE: "" } });
|
|
7844
|
+
return { calls: toolCallsInClaudeOutput(String(res.stdout ?? "")), status: res.status };
|
|
7845
|
+
}
|
|
7846
|
+
async function createPromptTester(caller, proj2, opts = {}) {
|
|
7847
|
+
const say = opts.say ?? ((s) => console.log(s));
|
|
7848
|
+
const deps = opts.deps ?? {};
|
|
7849
|
+
const claudeInstalled = deps.claudeInstalled ?? detectExternalClis().some((c) => c.kind === "claude");
|
|
7850
|
+
if (!claudeInstalled) {
|
|
7851
|
+
say(import_chalk26.default.dim(" (Claude Code is not installed here: each prompt is run at the end, in the exercise)"));
|
|
7852
|
+
return null;
|
|
7853
|
+
}
|
|
7854
|
+
const r = await fetchDirectoryReport(caller, proj2, { probe: false });
|
|
7855
|
+
const target2 = r.target ?? {};
|
|
7856
|
+
const publishedUrl = r.report.project.publishedUrl;
|
|
7857
|
+
if (!publishedUrl || target2.project?.directoryReady === false) return null;
|
|
7858
|
+
const open = target2.project?.authMode === "open";
|
|
7859
|
+
let bearer = null;
|
|
7860
|
+
let asMeta = null;
|
|
7861
|
+
if (!open) {
|
|
7862
|
+
const packet = await producer(caller, { method: "GET", path: `${projectPath(proj2)}/directory-packet?probe=0&force=1`, summary: "Reviewer account (for the login)" }).catch(() => null);
|
|
7863
|
+
const acct = packet?.json?.test_account ?? null;
|
|
7864
|
+
if (!acct) {
|
|
7865
|
+
say(import_chalk26.default.dim(" (no reviewer account yet: each prompt is run at the end, in the exercise)"));
|
|
7866
|
+
return null;
|
|
7867
|
+
}
|
|
7868
|
+
const login = await headlessPasswordLogin({ mcpUrl: publishedUrl, username: acct.username, password: acct.password }, deps);
|
|
7869
|
+
if (!login.accessToken) {
|
|
7870
|
+
say(import_chalk26.default.yellow(` (the reviewer could not sign in at ${login.failedAt}: ${login.detail}; each prompt is run at the end, in the exercise)`));
|
|
7871
|
+
return null;
|
|
7872
|
+
}
|
|
7873
|
+
bearer = login.accessToken;
|
|
7874
|
+
asMeta = login.as;
|
|
7875
|
+
}
|
|
7876
|
+
const opened = await producer(caller, { method: "POST", path: `${projectPath(proj2)}/directory-runs`, body: { runner: "cli-direct" }, summary: "Open a prompt-test run" });
|
|
7877
|
+
const runId2 = opened.run.run_id;
|
|
7878
|
+
const spawn5 = deps.spawn ?? import_child_process5.spawnSync;
|
|
7879
|
+
const configDir = import_fs5.default.mkdtempSync(import_path4.default.join(deps.tmpdir ?? import_os2.default.tmpdir(), "abz-prompt-"));
|
|
7880
|
+
import_fs5.default.chmodSync(configDir, 448);
|
|
7881
|
+
const cfgPath = import_path4.default.join(configDir, "mcp.json");
|
|
7882
|
+
import_fs5.default.writeFileSync(cfgPath, mcpConfigDocument(publishedUrl, bearer, runId2), { mode: 384 });
|
|
7883
|
+
const notes = [];
|
|
7884
|
+
return {
|
|
7885
|
+
async test(prompt) {
|
|
7886
|
+
const { calls } = claudeOneShot2(spawn5, cfgPath, prompt, ["mcp__abz__*"]);
|
|
7887
|
+
const note = { kind: "prompt", prompt, tool_called: calls.length > 0, tools: calls };
|
|
7888
|
+
const at = notes.findIndex((n) => n.prompt === prompt);
|
|
7889
|
+
if (at >= 0) notes[at] = note;
|
|
7890
|
+
else notes.push(note);
|
|
7891
|
+
return { called: calls.length > 0, tools: calls };
|
|
7892
|
+
},
|
|
7893
|
+
notes: () => [...notes],
|
|
7894
|
+
async close() {
|
|
7895
|
+
try {
|
|
7896
|
+
import_fs5.default.rmSync(configDir, { recursive: true, force: true });
|
|
7897
|
+
} catch {
|
|
7898
|
+
}
|
|
7899
|
+
if (bearer) await revokeToken(asMeta, bearer, void 0, deps).catch(() => false);
|
|
7900
|
+
await producer(caller, { method: "PATCH", path: `${projectPath(proj2)}/directory-runs/${encodeURIComponent(runId2)}`, body: { notes, runner: "cli-direct", email: false }, summary: "Close the prompt-test run" }).catch(() => null);
|
|
7901
|
+
}
|
|
7902
|
+
};
|
|
7866
7903
|
}
|
|
7867
7904
|
function mark(o) {
|
|
7868
|
-
return o === "ok" ?
|
|
7905
|
+
return o === "ok" ? import_chalk26.default.green("\u2713") : o === "error" ? import_chalk26.default.yellow("!") : import_chalk26.default.red("\u2717");
|
|
7869
7906
|
}
|
|
7870
7907
|
function parseResultJson(text) {
|
|
7871
7908
|
try {
|
|
@@ -7897,36 +7934,51 @@ async function runConnectorAnswers(project, opts = {}) {
|
|
|
7897
7934
|
if (health) meta.health_data = health;
|
|
7898
7935
|
if (sponsored) meta.sponsored_content = sponsored;
|
|
7899
7936
|
if (interactive2) {
|
|
7900
|
-
const current = await producer(caller, { method: "POST", path: `${
|
|
7937
|
+
const current = await producer(caller, { method: "POST", path: `${projectPath(proj2)}/directory-report`, body: { probe: false }, summary: "Current answers" });
|
|
7901
7938
|
const have = current.target?.metadata?.portalAnswers ?? {};
|
|
7902
7939
|
const { default: inquirer3 } = await import("inquirer");
|
|
7903
7940
|
const saveNow = async (part) => {
|
|
7904
7941
|
try {
|
|
7905
|
-
await producer(caller, { method: "PATCH", path:
|
|
7942
|
+
await producer(caller, { method: "PATCH", path: projectPath(proj2), body: part, summary: "Save an answer" });
|
|
7906
7943
|
} catch (e) {
|
|
7907
|
-
console.log(
|
|
7944
|
+
console.log(import_chalk26.default.yellow(` not saved: ${e instanceof Error ? e.message : String(e)}`));
|
|
7908
7945
|
}
|
|
7909
7946
|
};
|
|
7947
|
+
let headed = opts.derived !== false;
|
|
7948
|
+
const ask3 = async (qs) => {
|
|
7949
|
+
if (!headed) {
|
|
7950
|
+
headed = true;
|
|
7951
|
+
console.log(import_chalk26.default.bold(" What the portal asks that only you can answer (saved as you go):"));
|
|
7952
|
+
}
|
|
7953
|
+
return inquirer3.prompt(qs);
|
|
7954
|
+
};
|
|
7955
|
+
const desc = (current.target?.metadata?.description ?? "").trim();
|
|
7956
|
+
if (opts.derived !== false && desc.length < DESCRIPTION_MIN) {
|
|
7957
|
+
const a = await ask3([{ type: "input", name: "value", message: `Description (what people can do with it; ${DESCRIPTION_MIN}\u20132000 characters):`, default: desc || void 0, validate: (v) => v.trim().length >= DESCRIPTION_MIN ? true : `${v.trim().length}/${DESCRIPTION_MIN} characters \u2014 a reviewer needs to know what it does and what comes back` }]);
|
|
7958
|
+
meta.description = a.value.trim();
|
|
7959
|
+
await saveNow({ directory_metadata: { description: meta.description } });
|
|
7960
|
+
}
|
|
7910
7961
|
for (const q of QUESTIONS) {
|
|
7911
7962
|
if (answers[q.field] !== void 0) continue;
|
|
7963
|
+
if (opts.derived === false && (q.field === "use_cases" || q.field === "reviewer_instructions")) continue;
|
|
7912
7964
|
const existing = typeof have[q.field] === "string" ? String(have[q.field]) : "";
|
|
7913
7965
|
if (existing.length >= (q.min ?? 1)) continue;
|
|
7914
|
-
const a = await
|
|
7966
|
+
const a = await ask3([{ type: "input", name: "value", message: q.message, default: existing || void 0, validate: (v) => v.trim().length >= (q.min ?? 1) ? true : `${q.min ?? 1}+ characters` }]);
|
|
7915
7967
|
answers[q.field] = a.value.trim();
|
|
7916
7968
|
await saveNow({ directory_portal_answers: { [q.field]: a.value.trim() } });
|
|
7917
7969
|
}
|
|
7918
7970
|
if (answers.access === void 0 && !have.access) {
|
|
7919
|
-
const a = await
|
|
7971
|
+
const a = await ask3([{ type: "list", name: "value", message: "What the tools do:", choices: ["read", "write", "both"] }]);
|
|
7920
7972
|
answers.access = a.value;
|
|
7921
7973
|
await saveNow({ directory_portal_answers: { access: a.value } });
|
|
7922
7974
|
}
|
|
7923
7975
|
if (answers.categories === void 0 && !(Array.isArray(have.categories) && have.categories.length)) {
|
|
7924
|
-
const a = await
|
|
7976
|
+
const a = await ask3([{ type: "input", name: "value", message: "Category suggestions (1\u20135, comma-separated; you pick the closest in the portal):", validate: (v) => v.trim() ? true : "one at least" }]);
|
|
7925
7977
|
answers.categories = a.value.split(",").map((c) => c.trim()).filter(Boolean).slice(0, 5);
|
|
7926
7978
|
await saveNow({ directory_portal_answers: { categories: answers.categories } });
|
|
7927
7979
|
}
|
|
7928
7980
|
if (!meta.data_handling && !current.target?.metadata?.dataHandling) {
|
|
7929
|
-
const a = await
|
|
7981
|
+
const a = await ask3([
|
|
7930
7982
|
{ type: "list", name: "mode", message: "Whose API is behind this connector?", choices: [{ name: "own \u2014 my API, on my domain", value: "own" }, { name: "proxied \u2014 someone else's API I hold permission for", value: "proxied" }, { name: "third-party \u2014 someone else's API, no permission (cannot be listed)", value: "third-party" }] },
|
|
7931
7983
|
{ type: "input", name: "statement", message: "The permission you hold (who granted what, and when; goes into portal step 7):", when: (ans) => ans.mode === "proxied", validate: (v) => v.trim().length >= 20 ? true : "a sentence, please" }
|
|
7932
7984
|
]);
|
|
@@ -7934,12 +7986,12 @@ async function runConnectorAnswers(project, opts = {}) {
|
|
|
7934
7986
|
await saveNow({ directory_metadata: { data_handling: meta.data_handling } });
|
|
7935
7987
|
}
|
|
7936
7988
|
if (!meta.health_data && !current.target?.metadata?.healthData) {
|
|
7937
|
-
const a = await
|
|
7989
|
+
const a = await ask3([{ type: "confirm", name: "value", message: "Do the tools handle health data?", default: false }]);
|
|
7938
7990
|
meta.health_data = a.value ? "yes" : "no";
|
|
7939
7991
|
await saveNow({ directory_metadata: { health_data: meta.health_data } });
|
|
7940
7992
|
}
|
|
7941
7993
|
if (!meta.sponsored_content && !current.target?.metadata?.sponsoredContent) {
|
|
7942
|
-
const a = await
|
|
7994
|
+
const a = await ask3([{ type: "confirm", name: "value", message: "Do tool results carry sponsored content?", default: false }]);
|
|
7943
7995
|
meta.sponsored_content = a.value ? "yes" : "no";
|
|
7944
7996
|
await saveNow({ directory_metadata: { sponsored_content: meta.sponsored_content } });
|
|
7945
7997
|
}
|
|
@@ -7949,38 +8001,39 @@ async function runConnectorAnswers(project, opts = {}) {
|
|
|
7949
8001
|
if (Object.keys(meta).length) body.directory_metadata = meta;
|
|
7950
8002
|
if (!Object.keys(body).length) {
|
|
7951
8003
|
if (json) process.stdout.write(JSON.stringify({ project: proj2.projectId, changed: [] }) + "\n");
|
|
7952
|
-
else console.log(
|
|
8004
|
+
else if (opts.derived !== false) console.log(import_chalk26.default.dim(" Nothing to record: pass flags (--use-cases, --data-handling \u2026) or run interactively."));
|
|
7953
8005
|
return;
|
|
7954
8006
|
}
|
|
7955
|
-
await producer(caller, { method: "PATCH", path:
|
|
7956
|
-
const r = await fetchDirectoryReport(caller, proj2, { probe: false });
|
|
8007
|
+
await producer(caller, { method: "PATCH", path: projectPath(proj2), body, summary: "Store the portal answers" });
|
|
8008
|
+
const r = await fetchDirectoryReport(caller, proj2, { probe: false, ...opts.platforms ? { platforms: opts.platforms } : {} });
|
|
7957
8009
|
if (json) {
|
|
7958
8010
|
process.stdout.write(JSON.stringify({ project: proj2.projectId, changed: Object.keys({ ...answers, ...meta }), headline: r.rendered.headline, report: r.report }) + "\n");
|
|
7959
8011
|
return;
|
|
7960
8012
|
}
|
|
7961
|
-
console.log(
|
|
7962
|
-
console.log(
|
|
8013
|
+
console.log(import_chalk26.default.green(` \u2713 recorded: ${Object.keys({ ...answers, ...meta }).join(", ")}`));
|
|
8014
|
+
console.log(import_chalk26.default.dim(` ${r.rendered.headline}`));
|
|
7963
8015
|
}
|
|
7964
|
-
var
|
|
8016
|
+
var import_chalk26, import_fs5, import_os2, import_path4, import_child_process5, DASHBOARD_BASE8, RUN_META_KEY, RUN_HEADER, projectPath, QUESTIONS;
|
|
7965
8017
|
var init_connector_exercise = __esm({
|
|
7966
8018
|
"src/commands/connector-exercise.ts"() {
|
|
7967
8019
|
"use strict";
|
|
7968
|
-
|
|
7969
|
-
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
|
|
8020
|
+
import_chalk26 = __toESM(require("chalk"));
|
|
8021
|
+
import_fs5 = __toESM(require("fs"));
|
|
8022
|
+
import_os2 = __toESM(require("os"));
|
|
8023
|
+
import_path4 = __toESM(require("path"));
|
|
8024
|
+
import_child_process5 = require("child_process");
|
|
7973
8025
|
init_package();
|
|
7974
8026
|
init_caller();
|
|
7975
8027
|
init_directory();
|
|
7976
8028
|
init_headless_login();
|
|
7977
8029
|
init_exercise();
|
|
7978
8030
|
init_brand();
|
|
8031
|
+
init_portal_drafts();
|
|
7979
8032
|
init_external_mcp();
|
|
7980
8033
|
DASHBOARD_BASE8 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
|
|
7981
8034
|
RUN_META_KEY = "com.apiblaze/run";
|
|
7982
8035
|
RUN_HEADER = "apiblaze-directory-run";
|
|
7983
|
-
|
|
8036
|
+
projectPath = (p) => `/projects/${encodeURIComponent(p.projectId)}/${encodeURIComponent(p.apiVersion)}`;
|
|
7984
8037
|
QUESTIONS = [
|
|
7985
8038
|
{ key: "useCases", field: "use_cases", message: "Use cases (what a person asks it, in their words; 200+ characters):", min: 200 },
|
|
7986
8039
|
{ key: "prerequisites", field: "prerequisites", message: "Prerequisites (one sentence: what a user needs before it works):", min: 1 },
|
|
@@ -7992,6 +8045,225 @@ var init_connector_exercise = __esm({
|
|
|
7992
8045
|
}
|
|
7993
8046
|
});
|
|
7994
8047
|
|
|
8048
|
+
// src/lib/directory-docs.ts
|
|
8049
|
+
function directoryDocsPrompt(facts) {
|
|
8050
|
+
return [
|
|
8051
|
+
`Write the three documents a connector listing needs for "${facts.name}", from the FACTS below and nothing else.`,
|
|
8052
|
+
"Rules: plain English, second person, no marketing, no prices, no promo. Never invent a result: when you describe what a tool returns, cite only the recorded result shapes in the facts (keys, item counts, heads); if none is recorded, say what the tool is for and that the assistant will show the result.",
|
|
8053
|
+
"Never write credentials, usernames, tokens or internal hostnames. Troubleshooting cites the failure modes with their exact texts.",
|
|
8054
|
+
`The sign-in: ${facts.sign_in_shape}`,
|
|
8055
|
+
`Answer with JSON matching the schema: docs_markdown (the public page "Using <name> in Claude": purpose \xB7 how it works from Claude's side \xB7 connect and sign in \xB7 the example prompts with what to expect \xB7 troubleshooting \xB7 support), reviewer_markdown (for someone who has never seen the product: add the connector, the sign-in screen, the account, what data it holds, one prompt per tool with the recorded result shape), portal_answers (use_cases 200+ chars, prerequisites one sentence, access, 1\u20135 categories).`,
|
|
8056
|
+
"",
|
|
8057
|
+
"FACTS:",
|
|
8058
|
+
JSON.stringify(facts, null, 2)
|
|
8059
|
+
].join("\n");
|
|
8060
|
+
}
|
|
8061
|
+
function lintDocument(markdown, opts = {}) {
|
|
8062
|
+
const out = [];
|
|
8063
|
+
const text = markdown ?? "";
|
|
8064
|
+
if (!text.trim()) out.push({ code: "EMPTY", detail: "the document is empty" });
|
|
8065
|
+
if (text.length > DIRECTORY_DOC_MAX_CHARS) out.push({ code: "TOO_LONG", detail: `${text.length} characters; the limit is ${DIRECTORY_DOC_MAX_CHARS}` });
|
|
8066
|
+
const u = (opts.reviewerUsername ?? "").trim();
|
|
8067
|
+
if (u.length >= 3 && text.toLowerCase().includes(u.toLowerCase())) out.push({ code: "REVIEWER_USERNAME", detail: "the reviewer username appears; it belongs in the packet only" });
|
|
8068
|
+
if (BEARER_LIKE.test(text)) out.push({ code: "BEARER_LIKE", detail: "a bearer-like or JWT-shaped string appears" });
|
|
8069
|
+
if (KEY_LIKE.test(text)) out.push({ code: "KEY_LIKE", detail: "a key-like string (sk_/pk_/\u2026) appears" });
|
|
8070
|
+
if (PASSWORD_LINE.test(text)) out.push({ code: "PASSWORD_LINE", detail: "a password-like line appears" });
|
|
8071
|
+
if (opts.public && INTERNAL_HOST.test(text)) out.push({ code: "INTERNAL_HOST", detail: "an upstream or platform-internal host appears" });
|
|
8072
|
+
return out;
|
|
8073
|
+
}
|
|
8074
|
+
function parseGeneratedDocs(stdout) {
|
|
8075
|
+
const tryObj = (v) => {
|
|
8076
|
+
if (!v || typeof v !== "object") return null;
|
|
8077
|
+
const o = v;
|
|
8078
|
+
const pa = o.portal_answers;
|
|
8079
|
+
if (typeof o.docs_markdown !== "string" || typeof o.reviewer_markdown !== "string" || !pa || typeof pa !== "object") return null;
|
|
8080
|
+
return { docs_markdown: o.docs_markdown, reviewer_markdown: o.reviewer_markdown, portal_answers: { use_cases: String(pa.use_cases ?? ""), prerequisites: String(pa.prerequisites ?? ""), access: ["read", "write", "both"].includes(String(pa.access)) ? String(pa.access) : "both", categories: Array.isArray(pa.categories) ? pa.categories.map(String).slice(0, 5) : [] } };
|
|
8081
|
+
};
|
|
8082
|
+
let parsed = null;
|
|
8083
|
+
try {
|
|
8084
|
+
parsed = JSON.parse(stdout.trim());
|
|
8085
|
+
} catch {
|
|
8086
|
+
parsed = null;
|
|
8087
|
+
}
|
|
8088
|
+
if (parsed && typeof parsed === "object") {
|
|
8089
|
+
const o = parsed;
|
|
8090
|
+
const direct = tryObj(o);
|
|
8091
|
+
if (direct) return direct;
|
|
8092
|
+
const so = tryObj(o.structured_output);
|
|
8093
|
+
if (so) return so;
|
|
8094
|
+
if (typeof o.result === "string") {
|
|
8095
|
+
try {
|
|
8096
|
+
const r = tryObj(JSON.parse(o.result));
|
|
8097
|
+
if (r) return r;
|
|
8098
|
+
} catch {
|
|
8099
|
+
}
|
|
8100
|
+
}
|
|
8101
|
+
}
|
|
8102
|
+
for (const line of stdout.split("\n").reverse()) {
|
|
8103
|
+
const s = line.trim();
|
|
8104
|
+
if (!s.startsWith("{")) continue;
|
|
8105
|
+
try {
|
|
8106
|
+
const ev = JSON.parse(s);
|
|
8107
|
+
const so = tryObj(ev.structured_output);
|
|
8108
|
+
if (so) return so;
|
|
8109
|
+
if (typeof ev.result === "string") {
|
|
8110
|
+
const r = tryObj(JSON.parse(ev.result));
|
|
8111
|
+
if (r) return r;
|
|
8112
|
+
}
|
|
8113
|
+
} catch {
|
|
8114
|
+
}
|
|
8115
|
+
}
|
|
8116
|
+
return null;
|
|
8117
|
+
}
|
|
8118
|
+
var DIRECTORY_DOCS_JSON_SCHEMA, DIRECTORY_DOC_MAX_CHARS, BEARER_LIKE, KEY_LIKE, PASSWORD_LINE, INTERNAL_HOST;
|
|
8119
|
+
var init_directory_docs = __esm({
|
|
8120
|
+
"src/lib/directory-docs.ts"() {
|
|
8121
|
+
"use strict";
|
|
8122
|
+
DIRECTORY_DOCS_JSON_SCHEMA = {
|
|
8123
|
+
type: "object",
|
|
8124
|
+
additionalProperties: false,
|
|
8125
|
+
required: ["docs_markdown", "reviewer_markdown", "portal_answers"],
|
|
8126
|
+
properties: {
|
|
8127
|
+
docs_markdown: { type: "string", description: `The public documentation page, Markdown: "Using <name> in Claude" \u2014 purpose, how it works from Claude's side, connect and sign in, the three prompts with what to expect, troubleshooting (the known failure modes with their exact texts), support.` },
|
|
8128
|
+
reviewer_markdown: { type: "string", description: "Reviewer instructions for someone who has never seen the product: add the connector, the sign-in screen, the account, what data it holds, one prompt per tool with the recorded result shape. No credentials." },
|
|
8129
|
+
portal_answers: {
|
|
8130
|
+
type: "object",
|
|
8131
|
+
additionalProperties: false,
|
|
8132
|
+
required: ["use_cases", "prerequisites", "access", "categories"],
|
|
8133
|
+
properties: {
|
|
8134
|
+
use_cases: { type: "string", description: "What a person asks it, in their words; 200+ characters." },
|
|
8135
|
+
prerequisites: { type: "string", description: "What a user needs before it works, one sentence." },
|
|
8136
|
+
access: { type: "string", enum: ["read", "write", "both"] },
|
|
8137
|
+
categories: { type: "array", items: { type: "string" }, minItems: 1, maxItems: 5, description: "Free-text category suggestions; the producer picks the closest in the portal." }
|
|
8138
|
+
}
|
|
8139
|
+
}
|
|
8140
|
+
}
|
|
8141
|
+
};
|
|
8142
|
+
DIRECTORY_DOC_MAX_CHARS = 64e3;
|
|
8143
|
+
BEARER_LIKE = /(?:bearer\s+[A-Za-z0-9._~+/=-]{8,}|\b[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b)/i;
|
|
8144
|
+
KEY_LIKE = /\b(?:sk|pk|rk|key|tok|abz)_[A-Za-z0-9_-]{8,}\b/i;
|
|
8145
|
+
PASSWORD_LINE = /^\s*(?:[-*]\s*)?(?:\*\*)?(?:password|passwd|pwd|secret)(?:\*\*)?\s*[:=]\s*\S+/im;
|
|
8146
|
+
INTERNAL_HOST = /\b[a-z0-9.-]+\.(abz\.run|tryabz\.run|workers\.dev)\b/i;
|
|
8147
|
+
}
|
|
8148
|
+
});
|
|
8149
|
+
|
|
8150
|
+
// src/commands/connector-docs.ts
|
|
8151
|
+
var connector_docs_exports = {};
|
|
8152
|
+
__export(connector_docs_exports, {
|
|
8153
|
+
askDocsQuestion: () => askDocsQuestion,
|
|
8154
|
+
generateWithClaude: () => generateWithClaude,
|
|
8155
|
+
runConnectorDocs: () => runConnectorDocs,
|
|
8156
|
+
storeGeneratedDocs: () => storeGeneratedDocs,
|
|
8157
|
+
verifyDocsUrl: () => verifyDocsUrl
|
|
8158
|
+
});
|
|
8159
|
+
function generateWithClaude(prompt, deps = {}) {
|
|
8160
|
+
const spawn5 = deps.spawn ?? import_child_process6.spawnSync;
|
|
8161
|
+
const res = spawn5("claude", ["-p", prompt, "--output-format", "json", "--json-schema", JSON.stringify(DIRECTORY_DOCS_JSON_SCHEMA), "--tools", "", "--max-budget-usd", "2", "--permission-mode", "dontAsk"], { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], timeout: 24e4, env: { ...process.env, CLAUDECODE: "" } });
|
|
8162
|
+
const docs = parseGeneratedDocs(String(res.stdout ?? ""));
|
|
8163
|
+
return { docs, detail: docs ? "generated on Claude Code" : `Claude Code answered without the three documents${res.status != null && res.status !== 0 ? ` (exit ${res.status})` : ""}` };
|
|
8164
|
+
}
|
|
8165
|
+
async function storeGeneratedDocs(caller, proj2, docs, o) {
|
|
8166
|
+
const bad = [...lintDocument(docs.docs_markdown, { reviewerUsername: o.reviewerUsername, public: true }).map((f2) => `docs: ${f2.code}`), ...lintDocument(docs.reviewer_markdown, { reviewerUsername: o.reviewerUsername }).map((f2) => `reviewer: ${f2.code}`)];
|
|
8167
|
+
if (bad.length) throw new Error(`The generated documents were refused by the lint (${bad.join(", ")}); nothing was stored. Run again, or write them by hand on the dashboard page.`);
|
|
8168
|
+
const out = await producer(caller, { method: "PUT", path: `${projectPath2(proj2)}/directory-documents`, body: { docs_markdown: docs.docs_markdown, reviewer_markdown: docs.reviewer_markdown, portal_answers: docs.portal_answers, source: "generated", facts_hash: o.factsHash, use_generated_docs_url: o.useGeneratedDocsUrl }, summary: "Store the generated documents" });
|
|
8169
|
+
return out.changed ?? [];
|
|
8170
|
+
}
|
|
8171
|
+
async function verifyDocsUrl(url, serverName, f2 = fetch) {
|
|
8172
|
+
if (!/^https:\/\/\S+$/i.test(url)) return { ok: false, reason: "not an https URL" };
|
|
8173
|
+
try {
|
|
8174
|
+
const r = await f2(url, { redirect: "follow", signal: AbortSignal.timeout(8e3), headers: { accept: "text/html, */*;q=0.5", "user-agent": "apiblaze-cli directory-check" } });
|
|
8175
|
+
if (r.status !== 200) return { ok: false, reason: `answered ${r.status}` };
|
|
8176
|
+
const body = (await r.text().catch(() => "")).toLowerCase();
|
|
8177
|
+
if (serverName && !body.includes(serverName.toLowerCase())) return { ok: false, reason: `the page does not mention "${serverName}"` };
|
|
8178
|
+
return { ok: true, reason: "live" };
|
|
8179
|
+
} catch (e) {
|
|
8180
|
+
return { ok: false, reason: e instanceof Error ? e.message : "unreachable" };
|
|
8181
|
+
}
|
|
8182
|
+
}
|
|
8183
|
+
async function runConnectorDocs(project, opts = {}) {
|
|
8184
|
+
const caller = await requireCaller();
|
|
8185
|
+
const { teamId } = await resolveActingTeam(caller, opts.team);
|
|
8186
|
+
const proj2 = await resolveProjectVia(caller, teamId, project, opts.apiversion);
|
|
8187
|
+
const json = !!opts.json;
|
|
8188
|
+
const say = (s) => {
|
|
8189
|
+
if (!json) console.log(s);
|
|
8190
|
+
};
|
|
8191
|
+
const deps = opts.deps ?? {};
|
|
8192
|
+
const fr = await producer(caller, { method: "GET", path: `${projectPath2(proj2)}/directory-facts?view=packet`, summary: "The facts the generator reads" });
|
|
8193
|
+
const prompt = directoryDocsPrompt(fr.facts);
|
|
8194
|
+
let docs = null;
|
|
8195
|
+
let how = "";
|
|
8196
|
+
if (opts.from) {
|
|
8197
|
+
docs = parseGeneratedDocs(import_fs6.default.readFileSync(opts.from, "utf8"));
|
|
8198
|
+
if (!docs) throw new Error(`${opts.from} does not hold the three documents (docs_markdown, reviewer_markdown, portal_answers).`);
|
|
8199
|
+
how = `from ${opts.from}`;
|
|
8200
|
+
} else if (opts.promptOut) {
|
|
8201
|
+
import_fs6.default.writeFileSync(opts.promptOut, prompt + "\n", { mode: 384 });
|
|
8202
|
+
say(import_chalk27.default.green(` \u2713 prompt written to ${opts.promptOut}`));
|
|
8203
|
+
say(import_chalk27.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>`));
|
|
8204
|
+
return;
|
|
8205
|
+
} else {
|
|
8206
|
+
const installed3 = deps.claudeInstalled ?? detectExternalClis().some((c) => c.kind === "claude");
|
|
8207
|
+
if (!installed3) {
|
|
8208
|
+
const file2 = import_path5.default.join(import_os3.default.tmpdir(), `apiblaze-connector-docs-${proj2.projectId}.txt`);
|
|
8209
|
+
import_fs6.default.writeFileSync(file2, prompt + "\n", { mode: 384 });
|
|
8210
|
+
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.`);
|
|
8211
|
+
}
|
|
8212
|
+
say(import_chalk27.default.dim(" generating the three documents on Claude Code (30\u201390 s)\u2026"));
|
|
8213
|
+
const g = generateWithClaude(prompt, deps);
|
|
8214
|
+
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.`);
|
|
8215
|
+
docs = g.docs;
|
|
8216
|
+
how = g.detail;
|
|
8217
|
+
}
|
|
8218
|
+
const changed = await storeGeneratedDocs(caller, proj2, docs, { factsHash: fr.facts_hash, reviewerUsername: fr.facts.reviewer_username ?? null, useGeneratedDocsUrl: true });
|
|
8219
|
+
if (json) {
|
|
8220
|
+
process.stdout.write(JSON.stringify({ project: proj2.projectId, changed, docs_url: fr.generated_docs_url, how }) + "\n");
|
|
8221
|
+
return;
|
|
8222
|
+
}
|
|
8223
|
+
console.log(import_chalk27.default.green(` \u2713 documents stored (${how}): ${changed.join(", ")}`));
|
|
8224
|
+
console.log(` Documentation page: ${import_chalk27.default.cyan(fr.generated_docs_url)} \u2014 link it from your site; edit it on the dashboard any time.`);
|
|
8225
|
+
}
|
|
8226
|
+
async function askDocsQuestion(caller, proj2, state, interactive2, say, deps = {}) {
|
|
8227
|
+
if (!interactive2) return false;
|
|
8228
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
8229
|
+
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" }]);
|
|
8230
|
+
const v = a.value.trim();
|
|
8231
|
+
if (!v || /^n(o)?$/i.test(v)) {
|
|
8232
|
+
say(import_chalk27.default.dim(" using the generated page"));
|
|
8233
|
+
try {
|
|
8234
|
+
await runConnectorDocs(proj2.projectName, { deps, yes: true });
|
|
8235
|
+
} catch (e) {
|
|
8236
|
+
say(import_chalk27.default.yellow(` ${e instanceof Error ? e.message : String(e)}`));
|
|
8237
|
+
await producer(caller, { method: "PUT", path: `${projectPath2(proj2)}/directory-documents`, body: { use_generated_docs_url: true }, summary: "Point docs at the generated page" });
|
|
8238
|
+
}
|
|
8239
|
+
return true;
|
|
8240
|
+
}
|
|
8241
|
+
const check = await verifyDocsUrl(v, state.serverName, deps.fetch);
|
|
8242
|
+
if (!check.ok) {
|
|
8243
|
+
say(import_chalk27.default.yellow(` ${v} is not usable as documentation yet: ${check.reason}. Not stored.`));
|
|
8244
|
+
return false;
|
|
8245
|
+
}
|
|
8246
|
+
await producer(caller, { method: "PATCH", path: projectPath2(proj2), body: { directory_metadata: { docs_url: v, docs_confirmed: true } }, summary: "Store the documentation URL" });
|
|
8247
|
+
say(import_chalk27.default.green(` \u2713 docs: ${v} (live, names ${state.serverName ?? "the server"})`));
|
|
8248
|
+
return true;
|
|
8249
|
+
}
|
|
8250
|
+
var import_chalk27, import_fs6, import_os3, import_path5, import_child_process6, projectPath2;
|
|
8251
|
+
var init_connector_docs = __esm({
|
|
8252
|
+
"src/commands/connector-docs.ts"() {
|
|
8253
|
+
"use strict";
|
|
8254
|
+
import_chalk27 = __toESM(require("chalk"));
|
|
8255
|
+
import_fs6 = __toESM(require("fs"));
|
|
8256
|
+
import_os3 = __toESM(require("os"));
|
|
8257
|
+
import_path5 = __toESM(require("path"));
|
|
8258
|
+
import_child_process6 = require("child_process");
|
|
8259
|
+
init_caller();
|
|
8260
|
+
init_directory_docs();
|
|
8261
|
+
init_external_mcp();
|
|
8262
|
+
init_brand();
|
|
8263
|
+
projectPath2 = (p) => `/projects/${encodeURIComponent(p.projectId)}/${encodeURIComponent(p.apiVersion)}`;
|
|
8264
|
+
}
|
|
8265
|
+
});
|
|
8266
|
+
|
|
7995
8267
|
// src/lib/agent-chat.ts
|
|
7996
8268
|
async function runAgentChatRepl(opts) {
|
|
7997
8269
|
const { title, subtitle, endpoint, buildBody, seedPrompt, summarizeProposal, commands } = opts;
|
|
@@ -8172,10 +8444,10 @@ function derivedBehavior(method) {
|
|
|
8172
8444
|
}
|
|
8173
8445
|
function effectiveBehavior(method, o) {
|
|
8174
8446
|
const d = derivedBehavior(method);
|
|
8175
|
-
const
|
|
8176
|
-
const destructive =
|
|
8447
|
+
const readOnly2 = o?.readOnly ?? d.readOnly;
|
|
8448
|
+
const destructive = readOnly2 ? false : o?.destructive ?? d.destructive;
|
|
8177
8449
|
return {
|
|
8178
|
-
readOnly,
|
|
8450
|
+
readOnly: readOnly2,
|
|
8179
8451
|
destructive,
|
|
8180
8452
|
idempotent: o?.idempotent ?? d.idempotent,
|
|
8181
8453
|
confirm: o?.confirm ?? destructive,
|
|
@@ -11835,7 +12107,7 @@ function shortenTo(text, limit) {
|
|
|
11835
12107
|
const base2 = atWord >= Math.floor(limit * 0.4) ? cut.slice(0, atWord) : cut;
|
|
11836
12108
|
return base2.replace(/[\s,;:(\-–—]+$/g, "").trim();
|
|
11837
12109
|
}
|
|
11838
|
-
async function promptForMissing(r, interactive2, defaults = { prompts: [] }, platforms = PLATFORMS, given = {}, save) {
|
|
12110
|
+
async function promptForMissing(r, interactive2, defaults = { prompts: [] }, platforms = PLATFORMS, given = {}, save, testPrompt) {
|
|
11839
12111
|
const out = {};
|
|
11840
12112
|
const failing = r.report.results.filter((x) => !x.pass && !x.unverified);
|
|
11841
12113
|
const answered = (k) => {
|
|
@@ -11896,7 +12168,20 @@ async function promptForMissing(r, interactive2, defaults = { prompts: [] }, pla
|
|
|
11896
12168
|
if (i >= 3) break;
|
|
11897
12169
|
continue;
|
|
11898
12170
|
}
|
|
11899
|
-
|
|
12171
|
+
let text = line.trim();
|
|
12172
|
+
if (testPrompt) {
|
|
12173
|
+
let res = await testPrompt(text);
|
|
12174
|
+
if (!res.called) {
|
|
12175
|
+
say_(import_chalk30.default.yellow(` \u2013 Claude called no tool for "${text}". A prompt should say what the tool does (a real URL, name or id helps).`));
|
|
12176
|
+
const again = await inquirer3.prompt([{ type: "input", name: "line", message: "Reword it (Enter keeps it):", default: text, validate: withinLimit(a) }]);
|
|
12177
|
+
if (again.line?.trim() && again.line.trim() !== text) {
|
|
12178
|
+
text = again.line.trim();
|
|
12179
|
+
res = await testPrompt(text);
|
|
12180
|
+
}
|
|
12181
|
+
}
|
|
12182
|
+
say_(res.called ? import_chalk30.default.green(` \u2713 Claude called ${res.tools.join(", ")}`) : import_chalk30.default.yellow(" \u2013 still no tool call; kept (the report will say so)"));
|
|
12183
|
+
}
|
|
12184
|
+
lines.push(text);
|
|
11900
12185
|
}
|
|
11901
12186
|
if (lines.length) {
|
|
11902
12187
|
out.prompt = lines;
|
|
@@ -12214,7 +12499,6 @@ async function runConnector(target2, opts) {
|
|
|
12214
12499
|
throw e;
|
|
12215
12500
|
});
|
|
12216
12501
|
for (const f2 of fixes.applied ?? []) say(import_chalk30.default.green(` \u2713 fixed: ${f2}`));
|
|
12217
|
-
for (const s of fixes.skipped ?? []) say(import_chalk30.default.dim(` \u2013 ${s.fix}: ${s.reason}`));
|
|
12218
12502
|
if (fixes.applied?.length) r = await fetchDirectoryReport(caller, proj2, { platforms });
|
|
12219
12503
|
const domainWasBlocking = r.report.results.some((x) => x.id === DOMAIN_RULE && !x.pass);
|
|
12220
12504
|
const domain2 = await ensureMcpDomain(caller, proj2, r, opts, defaults, interactive2, say);
|
|
@@ -12224,6 +12508,15 @@ async function runConnector(target2, opts) {
|
|
|
12224
12508
|
if (changed) r = await fetchDirectoryReport(caller, proj2, { platforms });
|
|
12225
12509
|
}
|
|
12226
12510
|
if (await askOpenMode(caller, proj2, r, opts, interactive2, say)) r = await fetchDirectoryReport(caller, proj2, { platforms });
|
|
12511
|
+
let reviewer = null;
|
|
12512
|
+
if (r.report.results.some((x) => x.id === "TEST_ACCOUNT_SET" && !x.pass)) {
|
|
12513
|
+
reviewer = await ensureReviewerAccount(caller, teamId, proj2);
|
|
12514
|
+
if (!json) {
|
|
12515
|
+
console.log();
|
|
12516
|
+
printReviewer(reviewer, proj2);
|
|
12517
|
+
}
|
|
12518
|
+
r = await fetchDirectoryReport(caller, proj2, { platforms });
|
|
12519
|
+
}
|
|
12227
12520
|
const saveAnswer = async (f2) => {
|
|
12228
12521
|
try {
|
|
12229
12522
|
await patchProject(caller, proj2, { ...directoryBodyFromFlags(f2) }, "Save an answer");
|
|
@@ -12233,14 +12526,28 @@ async function runConnector(target2, opts) {
|
|
|
12233
12526
|
}
|
|
12234
12527
|
};
|
|
12235
12528
|
const basics = await promptListingBasics(r, interactive2, defaults, opts, platforms, interactive2 ? saveAnswer : void 0);
|
|
12529
|
+
let tester = null;
|
|
12530
|
+
let testerTried = false;
|
|
12531
|
+
const testPrompt = async (p) => {
|
|
12532
|
+
if (!tester && !testerTried) {
|
|
12533
|
+
testerTried = true;
|
|
12534
|
+
const { createPromptTester: createPromptTester2 } = await Promise.resolve().then(() => (init_connector_exercise(), connector_exercise_exports));
|
|
12535
|
+
tester = await createPromptTester2(caller, proj2, { say });
|
|
12536
|
+
}
|
|
12537
|
+
if (!tester) return { called: true, tools: [] };
|
|
12538
|
+
say(import_chalk30.default.dim(" trying it through Claude Code\u2026"));
|
|
12539
|
+
return tester.test(p);
|
|
12540
|
+
};
|
|
12236
12541
|
if (!isReady(r, platforms) || Object.keys(basics).length) {
|
|
12237
|
-
const asked = { ...basics, ...isReady(r, platforms) ? {} : await promptForMissing(r, interactive2, defaults, platforms, basics, interactive2 ? saveAnswer : void 0) };
|
|
12542
|
+
const asked = { ...basics, ...isReady(r, platforms) ? {} : await promptForMissing(r, interactive2, defaults, platforms, basics, interactive2 ? saveAnswer : void 0, interactive2 && opts.exercise !== false ? testPrompt : void 0) };
|
|
12238
12543
|
const body = directoryBodyFromFlags(asked);
|
|
12239
12544
|
if (body.directory_metadata || body.directory_attestations || body.openai_challenge_token) {
|
|
12240
12545
|
await patchProject(caller, proj2, { ...body }, "Save listing details");
|
|
12241
12546
|
r = await fetchDirectoryReport(caller, proj2, { platforms });
|
|
12242
12547
|
}
|
|
12243
12548
|
}
|
|
12549
|
+
const provenPromptNotes = tester ? tester.notes() : [];
|
|
12550
|
+
if (tester) await tester.close();
|
|
12244
12551
|
const DOCS_RULES = ["DOCS_URL", "DOCS_PAGE_LIVE", "DOCS_IS_PROSE"];
|
|
12245
12552
|
if (interactive2 && r.report.results.some((x) => DOCS_RULES.includes(x.id) && !x.pass)) {
|
|
12246
12553
|
const { askDocsQuestion: askDocsQuestion2 } = await Promise.resolve().then(() => (init_connector_docs(), connector_docs_exports));
|
|
@@ -12248,30 +12555,28 @@ async function runConnector(target2, opts) {
|
|
|
12248
12555
|
const generated = `https://${proj2.tenant ?? teamId}.portal.apiblaze.com/connector`;
|
|
12249
12556
|
if (await askDocsQuestion2(caller, proj2, { docsUrl: meta.docsUrl ?? null, serverName: meta.serverName ?? null, generatedDocsUrl: generated }, interactive2, say)) r = await fetchDirectoryReport(caller, proj2, { platforms });
|
|
12250
12557
|
}
|
|
12251
|
-
let reviewer = null;
|
|
12252
|
-
if (r.report.results.some((x) => x.id === "TEST_ACCOUNT_SET" && !x.pass)) {
|
|
12253
|
-
reviewer = await ensureReviewerAccount(caller, teamId, proj2);
|
|
12254
|
-
if (!json) {
|
|
12255
|
-
console.log();
|
|
12256
|
-
printReviewer(reviewer, proj2);
|
|
12257
|
-
}
|
|
12258
|
-
r = await fetchDirectoryReport(caller, proj2, { platforms });
|
|
12259
|
-
}
|
|
12260
12558
|
if (interactive2 && r.report.results.some((x) => ANSWER_RULES.includes(x.id) && !x.pass)) {
|
|
12261
12559
|
const { runConnectorAnswers: runConnectorAnswers2 } = await Promise.resolve().then(() => (init_connector_exercise(), connector_exercise_exports));
|
|
12262
|
-
|
|
12263
|
-
await runConnectorAnswers2(proj2.projectName, { team: opts.team, apiversion: opts.apiversion });
|
|
12560
|
+
await runConnectorAnswers2(proj2.projectName, { team: opts.team, apiversion: opts.apiversion, platforms: [...platforms], derived: false });
|
|
12264
12561
|
r = await fetchDirectoryReport(caller, proj2, { platforms });
|
|
12265
12562
|
}
|
|
12266
12563
|
if (opts.exercise !== false) {
|
|
12267
12564
|
let run2 = !!opts.yes;
|
|
12268
12565
|
if (interactive2) {
|
|
12269
12566
|
const { default: inquirer3 } = await import("inquirer");
|
|
12270
|
-
run2 = (await inquirer3.prompt([{ type: "confirm", name: "run", message:
|
|
12567
|
+
run2 = (await inquirer3.prompt([{ type: "confirm", name: "run", message: `Run every tool now, ${isOpenMode(r) ? "the way a reviewer would" : "as the reviewer"}, so the report can prove it works?`, default: true }])).run;
|
|
12271
12568
|
}
|
|
12272
12569
|
if (run2) {
|
|
12273
12570
|
const { runConnectorExercise: runConnectorExercise2 } = await Promise.resolve().then(() => (init_connector_exercise(), connector_exercise_exports));
|
|
12274
|
-
await runConnectorExercise2(proj2.projectName, { team: opts.team, apiversion: opts.apiversion, json, yes: opts.yes, noEmail: opts.email === false, report: false, platforms: [...platforms] });
|
|
12571
|
+
const ran = await runConnectorExercise2(proj2.projectName, { team: opts.team, apiversion: opts.apiversion, json, yes: opts.yes, noEmail: opts.email === false, report: false, platforms: [...platforms], provenPromptNotes });
|
|
12572
|
+
if (interactive2 && ran.failedPrompts.length) {
|
|
12573
|
+
const { rewritten, prompts } = await rewriteFailedPrompts(r, ran.failedPrompts, saveAnswer, say);
|
|
12574
|
+
if (rewritten.length) {
|
|
12575
|
+
say(import_chalk30.default.dim(` Running the ${prompts.length} prompts through Claude Code again (the report reads the last run)\u2026`));
|
|
12576
|
+
await runConnectorExercise2(proj2.projectName, { team: opts.team, apiversion: opts.apiversion, json, noEmail: opts.email === false, report: false, platforms: [...platforms], promptsOnly: prompts });
|
|
12577
|
+
}
|
|
12578
|
+
}
|
|
12579
|
+
if (interactive2 || opts.yes) await offerDrafts(caller, proj2, r, ran, interactive2, say);
|
|
12275
12580
|
r = await fetchDirectoryReport(caller, proj2, { platforms, publishedUrl: r.report.project.publishedUrl ?? null });
|
|
12276
12581
|
}
|
|
12277
12582
|
}
|
|
@@ -12283,7 +12588,8 @@ async function runConnector(target2, opts) {
|
|
|
12283
12588
|
exitWithReport(r, proj2, platforms, json, interactive2);
|
|
12284
12589
|
}
|
|
12285
12590
|
r = await fetchDirectoryReport(caller, proj2, { platforms, refreshSnapshot: true, publishedUrl: r.report.project.publishedUrl ?? null });
|
|
12286
|
-
const
|
|
12591
|
+
const probes = await packetProbes(r);
|
|
12592
|
+
const packet = await producer(caller, { method: "POST", path: `${projectPath3(proj2)}/directory-packet`, body: { platforms, ...probes ? { probes } : {} }, summary: "Submission packet" });
|
|
12287
12593
|
const dir = opts.out ?? process.cwd();
|
|
12288
12594
|
const mdPath = import_path6.default.join(dir, `connector-packet-${proj2.projectId}.md`);
|
|
12289
12595
|
const jsonPath = import_path6.default.join(dir, `connector-packet-${proj2.projectId}.json`);
|
|
@@ -12303,10 +12609,91 @@ async function runConnector(target2, opts) {
|
|
|
12303
12609
|
console.log(import_chalk30.default.green.bold(` ${r.rendered.headline}`));
|
|
12304
12610
|
console.log(` Submission packet: ${import_chalk30.default.cyan(mdPath)} and ${import_chalk30.default.cyan(jsonPath)}`);
|
|
12305
12611
|
console.log(import_chalk30.default.dim(isOpenMode(r) ? " (open mode: no reviewer credentials in the packet; the connector takes no sign-in)" : " (the packet carries the reviewer credentials \u2014 keep it out of git)"));
|
|
12306
|
-
printSubmitSteps(r, mdPath);
|
|
12612
|
+
printSubmitSteps(r, mdPath, platforms);
|
|
12307
12613
|
console.log(import_chalk30.default.dim(` ${r.report.disclaimer}`));
|
|
12308
12614
|
}
|
|
12615
|
+
async function rewriteFailedPrompts(r, failed, save, say) {
|
|
12616
|
+
const current = currentListingValue(r, "prompt");
|
|
12617
|
+
const list = Array.isArray(current) ? [...current] : [];
|
|
12618
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
12619
|
+
const rewritten = [];
|
|
12620
|
+
say(import_chalk30.default.yellow(` ${failed.length === 1 ? "This prompt" : `${failed.length} prompts`} made Claude call no tool; a prompt should name what the tool does${failed.length === 1 ? "" : ", one at a time"}:`));
|
|
12621
|
+
for (const old of failed) {
|
|
12622
|
+
const { value } = await inquirer3.prompt([{ type: "input", name: "value", message: `Rewrite "${old}" (Enter keeps it):`, default: old, validate: (v) => v.trim().length <= 300 ? true : `${v.trim().length} characters; 300 at most` }]);
|
|
12623
|
+
const next = (value ?? "").trim();
|
|
12624
|
+
if (!next || next === old) continue;
|
|
12625
|
+
const at = list.indexOf(old);
|
|
12626
|
+
if (at >= 0) list[at] = next;
|
|
12627
|
+
else list.push(next);
|
|
12628
|
+
rewritten.push(next);
|
|
12629
|
+
}
|
|
12630
|
+
if (rewritten.length) {
|
|
12631
|
+
const why = await save({ prompt: list });
|
|
12632
|
+
if (why) {
|
|
12633
|
+
say_(import_chalk30.default.yellow(` not saved: ${why}`));
|
|
12634
|
+
return { rewritten: [], prompts: list };
|
|
12635
|
+
}
|
|
12636
|
+
}
|
|
12637
|
+
return { rewritten, prompts: list };
|
|
12638
|
+
}
|
|
12639
|
+
async function packetProbes(r) {
|
|
12640
|
+
const publishedUrl = r.report.project.publishedUrl;
|
|
12641
|
+
if (!publishedUrl) return null;
|
|
12642
|
+
const meta = r.target?.metadata ?? {};
|
|
12643
|
+
try {
|
|
12644
|
+
const { probeDirectoryTarget: probeDirectoryTarget2 } = await Promise.resolve().then(() => (init_directory_probes(), directory_probes_exports));
|
|
12645
|
+
return await probeDirectoryTarget2({ publishedUrl, privacyUrl: meta.privacyUrl ?? null, docsUrl: meta.docsUrl ?? null, serverName: meta.serverName ?? null });
|
|
12646
|
+
} catch {
|
|
12647
|
+
return null;
|
|
12648
|
+
}
|
|
12649
|
+
}
|
|
12650
|
+
async function offerDrafts(caller, proj2, r, ran, interactive2, say) {
|
|
12651
|
+
if (!ran || !Array.isArray(ran.rows) || !ran.publishedUrl) return false;
|
|
12652
|
+
const { draftMissing: draftMissing2, ANSWER_MIN: ANSWER_MIN2 } = await Promise.resolve().then(() => (init_portal_drafts(), portal_drafts_exports));
|
|
12653
|
+
const meta = r.target?.metadata ?? {};
|
|
12654
|
+
const prompts = (meta.examplePrompts ?? []).filter((p) => typeof p === "string" && !!p.trim());
|
|
12655
|
+
const drafts = draftMissing2({
|
|
12656
|
+
publishedUrl: ran.publishedUrl,
|
|
12657
|
+
open: ran.open,
|
|
12658
|
+
reviewerUsername: ran.reviewerUsername,
|
|
12659
|
+
tools: ran.tools,
|
|
12660
|
+
rows: ran.rows,
|
|
12661
|
+
prompts,
|
|
12662
|
+
promptTools: ran.promptTools,
|
|
12663
|
+
serverName: meta.serverName,
|
|
12664
|
+
tagline: meta.tagline,
|
|
12665
|
+
description: meta.description
|
|
12666
|
+
}, { use_cases: meta.portalAnswers?.use_cases, reviewer_instructions: meta.portalAnswers?.reviewer_instructions, description: meta.description });
|
|
12667
|
+
if (!drafts.length) return false;
|
|
12668
|
+
const inquirer3 = interactive2 ? (await import("inquirer")).default : null;
|
|
12669
|
+
let saved = false;
|
|
12670
|
+
say("");
|
|
12671
|
+
say(import_chalk30.default.bold(` Written from the run \u2014 the portal's ${drafts.map((d) => d.label.toLowerCase()).join(", ")}${drafts.length > 1 ? "" : ""}:`));
|
|
12672
|
+
for (const d of drafts) {
|
|
12673
|
+
let text = d.draft;
|
|
12674
|
+
say("");
|
|
12675
|
+
say(import_chalk30.default.bold(` ${d.label}`) + import_chalk30.default.dim(` (${d.draft.length} characters; the portal wants ${ANSWER_MIN2[d.key]}+)`));
|
|
12676
|
+
for (const line of d.draft.split("\n")) say(` ${line}`);
|
|
12677
|
+
if (inquirer3) {
|
|
12678
|
+
const { ok } = await inquirer3.prompt([{ type: "confirm", name: "ok", message: `Use this ${d.label.toLowerCase()}? (n = type your own)`, default: true }]);
|
|
12679
|
+
if (!ok) {
|
|
12680
|
+
const { value } = await inquirer3.prompt([{ type: "input", name: "value", message: `${d.label}:`, ...d.current ? { default: d.current } : {}, validate: (v) => v.trim().length >= ANSWER_MIN2[d.key] ? true : `${v.trim().length}/${ANSWER_MIN2[d.key]} characters` }]);
|
|
12681
|
+
text = value.trim();
|
|
12682
|
+
}
|
|
12683
|
+
}
|
|
12684
|
+
const body = d.key === "description" ? { directory_metadata: { description: text } } : { directory_portal_answers: { [d.key]: text } };
|
|
12685
|
+
try {
|
|
12686
|
+
await patchProject(caller, proj2, body, `Save the ${d.label.toLowerCase()}`);
|
|
12687
|
+
saved = true;
|
|
12688
|
+
say(import_chalk30.default.green(` \u2713 ${d.label.toLowerCase()} saved`));
|
|
12689
|
+
} catch (e) {
|
|
12690
|
+
say(import_chalk30.default.yellow(` not saved: ${e instanceof Error ? e.message : String(e)}`));
|
|
12691
|
+
}
|
|
12692
|
+
}
|
|
12693
|
+
return saved;
|
|
12694
|
+
}
|
|
12309
12695
|
var SNAPSHOT_RULE = "LISTING_SNAPSHOT_MATCH";
|
|
12696
|
+
var withProject = (s, projectId) => s.replace(/<project>/g, projectId);
|
|
12310
12697
|
var ANSWER_RULES = ["PORTAL_ANSWERS_COMPLETE", "HEALTH_SPONSORED_DECLARED", "API_OWNERSHIP_DOMAIN"];
|
|
12311
12698
|
function exitWithReport(r, proj2, platforms, json, interactive2 = false) {
|
|
12312
12699
|
if (json) {
|
|
@@ -12321,8 +12708,8 @@ function exitWithReport(r, proj2, platforms, json, interactive2 = false) {
|
|
|
12321
12708
|
console.error(import_chalk30.default.red(`
|
|
12322
12709
|
Not ready: ${blocking.length} blocker${blocking.length === 1 ? "" : "s"} (${blocking.map((x) => x.id).join(", ")}).`));
|
|
12323
12710
|
const name = proj2.projectId;
|
|
12324
|
-
if (
|
|
12325
|
-
else if (first) console.error(` Next: ${first.fix ?? first.message ?? first.title}`);
|
|
12711
|
+
if (real.some((x) => ANSWER_RULES.includes(x.id))) console.error(` Next: run \`${B.cli} connector ${name}\` in a terminal with the exercise on \u2014 it asks what only you can answer and writes the use cases, the reviewer instructions and the description from the run; or pass them: \`${B.cli} connector answers ${name} --use-cases \u2026 --data-handling own --health-data no --sponsored-content no\`.`);
|
|
12712
|
+
else if (first) console.error(` Next: ${withProject(first.fix ?? first.message ?? first.title, name)}`);
|
|
12326
12713
|
console.error(import_chalk30.default.dim(` Report page: ${DASHBOARD_BASE6}/directory/${encodeURIComponent(proj2.projectId)}/${encodeURIComponent(proj2.apiVersion)}`));
|
|
12327
12714
|
process.exit(1);
|
|
12328
12715
|
}
|
|
@@ -12438,15 +12825,21 @@ async function headStatus(u) {
|
|
|
12438
12825
|
return 0;
|
|
12439
12826
|
}
|
|
12440
12827
|
}
|
|
12441
|
-
function printSubmitSteps(r, mdPath) {
|
|
12828
|
+
function printSubmitSteps(r, mdPath, platforms = PLATFORMS) {
|
|
12442
12829
|
const url = r.report.project.publishedUrl ?? "(the MCP URL in the packet)";
|
|
12443
12830
|
const open = isOpenMode(r);
|
|
12831
|
+
const claude = platforms.includes("claude");
|
|
12832
|
+
const chatgpt = platforms.includes("chatgpt");
|
|
12833
|
+
const where = [claude ? "Claude (Settings \u2192 Connectors)" : null, chatgpt ? "ChatGPT (Developer mode)" : null].filter(Boolean).join(" and in ");
|
|
12834
|
+
const steps = [
|
|
12835
|
+
`Test it yourself first: add ${import_chalk30.default.cyan(url)} as a custom connector in ${where}, ${open ? "no sign-in needed (open mode)" : "sign in with the reviewer account"}, run a tool.`,
|
|
12836
|
+
...claude ? [`Claude: ${import_chalk30.default.underline(PORTALS.claude)} \u2192 Submit new \u2192 MCP connector. Paste the listing, ${open ? "Authentication: None with the access instructions" : "the reviewer credentials"} and the test steps from ${import_chalk30.default.cyan(import_path6.default.basename(mdPath))}.`] : [],
|
|
12837
|
+
...chatgpt ? [`ChatGPT: ${import_chalk30.default.underline(PORTALS.chatgpt)} \u2192 ${claude ? "the same packet" : `the packet (${import_chalk30.default.cyan(import_path6.default.basename(mdPath))})`}, plus the 5 + 3 test cases it lists.`] : [],
|
|
12838
|
+
open ? "A nightly re-check emails you if the listing drifts." : "Keep the reviewer account alive; a nightly re-check emails you if the listing drifts."
|
|
12839
|
+
];
|
|
12444
12840
|
console.log();
|
|
12445
12841
|
console.log(import_chalk30.default.bold(" Submit (you do this part; the packet has every value to paste):"));
|
|
12446
|
-
console.log(`
|
|
12447
|
-
console.log(` 2. Claude: ${import_chalk30.default.underline(PORTALS.claude)} \u2192 Submit new \u2192 MCP connector. Paste the listing, ${open ? "Authentication: None with the access instructions" : "the reviewer credentials"} and the test steps from ${import_chalk30.default.cyan(import_path6.default.basename(mdPath))}.`);
|
|
12448
|
-
console.log(` 3. ChatGPT: ${import_chalk30.default.underline(PORTALS.chatgpt)} \u2192 the same packet, plus the 5 + 3 test cases it lists.`);
|
|
12449
|
-
console.log(` 4. Keep the reviewer account alive; a nightly re-check emails you if the listing drifts.`);
|
|
12842
|
+
steps.forEach((s, i) => console.log(` ${i + 1}. ${s}`));
|
|
12450
12843
|
console.log();
|
|
12451
12844
|
}
|
|
12452
12845
|
|
|
@@ -21261,7 +21654,9 @@ var connector = program.command("connector").description(`A compliant Claude con
|
|
|
21261
21654
|
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>`);
|
|
21262
21655
|
await runConnector(project, opts);
|
|
21263
21656
|
}));
|
|
21264
|
-
connector.command("exercise").description("Call every tool the way a reviewer would, as the reviewer account (open mode: as you): reads with a valid and an invalid argument, writes only on ids the run created and only after a y, then the three listing prompts through Claude Code when it is installed. The gateway records the outcomes; the report reads them.").argument("<project>", `The ${B.thingShort} (project name)`).option("--empty-ok <tool>", 'A read-only tool whose correct answer is "nothing" (repeatable)', collect, []).option("--no-claude", "Skip the Claude Code pass").option("--no-email", "No email at the end of the run").option("-y, --yes", "No questions: writes are not exercised (they need a y), the rest runs").option("--team <id|name>", "Team the project is in (defaults to your active team)").option("--apiversion <version>", "API version (defaults to the project's)").option("--json", "Output machine-readable JSON").action(action((project, opts) =>
|
|
21657
|
+
connector.command("exercise").description("Call every tool the way a reviewer would, as the reviewer account (open mode: as you): reads with a valid and an invalid argument, writes only on ids the run created and only after a y, then the three listing prompts through Claude Code when it is installed. The gateway records the outcomes; the report reads them.").argument("<project>", `The ${B.thingShort} (project name)`).option("--empty-ok <tool>", 'A read-only tool whose correct answer is "nothing" (repeatable)', collect, []).option("--no-claude", "Skip the Claude Code pass").option("--no-email", "No email at the end of the run").option("-y, --yes", "No questions: writes are not exercised (they need a y), the rest runs").option("--team <id|name>", "Team the project is in (defaults to your active team)").option("--apiversion <version>", "API version (defaults to the project's)").option("--json", "Output machine-readable JSON").action(action(async (project, opts) => {
|
|
21658
|
+
await runConnectorExercise(project, { ...withConnectorOpts(opts), noClaude: opts.claude === false, noEmail: opts.email === false });
|
|
21659
|
+
}));
|
|
21265
21660
|
connector.command("docs").description('Generate the three documents a listing needs \u2014 the public page "Using <name> in Claude" (served at <portal>/connector), the reviewer instructions and the portal answers \u2014 on your Claude Code from the facts of the connector; lint them, store them, point the docs URL at the page. Edit them on the dashboard any time.').argument("<project>", `The ${B.thingShort} (project name)`).option("--from <file>", "Paste back a JSON answer produced elsewhere (see --prompt-out)").option("--prompt-out <file>", "Write the prompt + facts to a file instead of running Claude Code").option("--team <id|name>", "Team the project is in (defaults to your active team)").option("--apiversion <version>", "API version (defaults to the project's)").option("--json", "Output machine-readable JSON").action(action((project, opts) => runConnectorDocs(project, withConnectorOpts(opts))));
|
|
21266
21661
|
connector.command("answers").description("The portal's free-text steps 3\u20138 (use cases, prerequisites, access, categories, company, data handling, health data, sponsored content, reviewer instructions): asked when missing, or passed as flags").argument("<project>", `The ${B.thingShort} (project name)`).option("--use-cases <text>", "Use cases (200+ characters)").option("--prerequisites <text>", "What a user needs before it works (one sentence)").option("--access <read|write|both>", "What the tools do").option("--category <name>", "A category suggestion (repeatable, up to five; you pick the closest in the portal)", collect, []).option("--company-name <name>", "Company name").option("--company-website <url>", "Company website (https)").option("--company-contact <email|url>", "Primary contact").option("--reviewer-instructions <text>", "How to add it, sign in, one prompt per tool and what to expect (400+ characters)").option("--data-handling <mode>", "own \xB7 proxied (add --data-handling-statement) \xB7 third-party").option("--data-handling-statement <text>", "For proxied: the permission you hold").option("--health-data <yes|no>", "The tools handle health data").option("--sponsored-content <yes|no>", "Tool results carry sponsored content").option("-y, --yes", "No questions: record the flags only").option("--team <id|name>", "Team the project is in (defaults to your active team)").option("--apiversion <version>", "API version (defaults to the project's)").option("--json", "Output machine-readable JSON").action(action((project, opts) => runConnectorAnswers(project, withConnectorOpts(opts))));
|
|
21267
21662
|
var withConnectorOpts = (opts) => ({ ...connector.opts(), ...opts ?? {} });
|