@exposify/cli 0.0.7 → 0.0.8
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 +100 -63
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -235,6 +235,13 @@ var red = paint("31");
|
|
|
235
235
|
var green = paint("32");
|
|
236
236
|
var yellow = paint("33");
|
|
237
237
|
var cyan = paint("36");
|
|
238
|
+
function stepRail(n, total) {
|
|
239
|
+
return cyan("\u25CF".repeat(n) + "\u25CB".repeat(Math.max(0, total - n)));
|
|
240
|
+
}
|
|
241
|
+
function stepHead(guided, n, total, text) {
|
|
242
|
+
return `
|
|
243
|
+
${guided ? `${stepRail(n, total)} ` : ""}${bold(text)}`;
|
|
244
|
+
}
|
|
238
245
|
function groupOf(path) {
|
|
239
246
|
const literal = [];
|
|
240
247
|
for (const seg of path.split("/").filter(Boolean)) {
|
|
@@ -243,34 +250,34 @@ function groupOf(path) {
|
|
|
243
250
|
}
|
|
244
251
|
return `/${literal.join("/")}`;
|
|
245
252
|
}
|
|
246
|
-
function rowText(r, state, focused2) {
|
|
253
|
+
function rowText(r, state, focused2, showReasons) {
|
|
247
254
|
const ticked = state.selected.has(routeKey(r));
|
|
248
255
|
const box = isLocked(r) ? "[\xB7]" : ticked ? green("[x]") : "[ ]";
|
|
249
256
|
const method = r.method.padEnd(6);
|
|
250
257
|
const path = r.path.padEnd(36);
|
|
251
258
|
let note;
|
|
252
259
|
if (r.marker) note = dim(`(${r.marker === "described" ? "describe()" : `${r.marker}()`} in code)`);
|
|
253
|
-
else note = dim(r.derived ?? "");
|
|
260
|
+
else note = showReasons ? dim(r.derived ?? "") : "";
|
|
254
261
|
const write = !isRead(r) && !isLocked(r) ? yellow(" <- write") : "";
|
|
255
|
-
const line = ` ${box} ${isRead(r) ? method : yellow(method)} ${path} ${note}${write}
|
|
262
|
+
const line = ` ${box} ${isRead(r) ? method : yellow(method)} ${path} ${note}${write}`.trimEnd();
|
|
256
263
|
return focused2 ? `${cyan(">")}${line.slice(1)}` : line;
|
|
257
264
|
}
|
|
258
265
|
var ACTION_STATUS = { complete: "input known", partial: "input partly unknown", flagged: "flagged" };
|
|
259
|
-
function actionText(a, state, focused2) {
|
|
266
|
+
function actionText(a, state, focused2, showReasons) {
|
|
260
267
|
const box = isLocked(a) ? "[\xB7]" : state.selectedActions.has(a.configKey) ? green("[x]") : "[ ]";
|
|
261
268
|
const where = dim(`${a.file}:${a.line}`.padEnd(36));
|
|
262
|
-
const note = isLocked(a) ? dim(`(${a.marker === "described" ? "describe()" : `${a.marker}()`} in code)`) : a.status === "complete" ? dim(ACTION_STATUS[a.status]) : yellow(ACTION_STATUS[a.status]);
|
|
263
|
-
const line = ` ${box} ${yellow(a.export.padEnd(24))} ${where} ${note}
|
|
269
|
+
const note = isLocked(a) ? dim(`(${a.marker === "described" ? "describe()" : `${a.marker}()`} in code)`) : showReasons ? a.status === "complete" ? dim(ACTION_STATUS[a.status]) : yellow(ACTION_STATUS[a.status]) : "";
|
|
270
|
+
const line = ` ${box} ${yellow(a.export.padEnd(24))} ${where} ${note}`.trimEnd();
|
|
264
271
|
return focused2 ? `${cyan(">")}${line.slice(1)}` : line;
|
|
265
272
|
}
|
|
266
|
-
function render(state, height, filtering) {
|
|
273
|
+
function render(state, height, filtering, showReasons = false) {
|
|
267
274
|
const out = [];
|
|
268
|
-
out.push(bold("
|
|
275
|
+
out.push(`${stepRail(1, 5)} ${bold("Which endpoints should agents be able to call?")}`);
|
|
269
276
|
if (state.nuclear) {
|
|
270
277
|
out.push(red(bold(' expose: "*" -- every write route is exposed, including ones added later.')));
|
|
271
278
|
out.push(dim(" Un-tick rows to exclude them \xB7 x back to an explicit list \xB7 enter confirm"));
|
|
272
279
|
} else {
|
|
273
|
-
out.push(dim(
|
|
280
|
+
out.push(dim(` Reads are pre-selected. space toggle \xB7 a all \xB7 n none \xB7 r reads only \xB7 / filter \xB7 ? ${showReasons ? "hide" : "why"}`));
|
|
274
281
|
out.push(dim(' ! expose everything (expose: "*"), asks for confirmation \xB7 enter confirm \xB7 ctrl-c quit'));
|
|
275
282
|
}
|
|
276
283
|
out.push(filtering || state.filter ? ` filter: ${state.filter}${filtering ? "\u258F" : ""}` : "");
|
|
@@ -283,15 +290,15 @@ function render(state, height, filtering) {
|
|
|
283
290
|
group = g;
|
|
284
291
|
lines.push({ text: bold(` ${g}`) });
|
|
285
292
|
}
|
|
286
|
-
lines.push({ text: rowText(r, state, i === state.cursor), rowIndex: i });
|
|
293
|
+
lines.push({ text: rowText(r, state, i === state.cursor, showReasons), rowIndex: i });
|
|
287
294
|
});
|
|
288
295
|
const actions = visibleActions(state);
|
|
289
296
|
if (actions.length > 0) {
|
|
290
297
|
lines.push({ text: `${bold(" Server Actions")} ${dim("(writes: space picks each one; a and ! leave them alone)")}` });
|
|
291
298
|
actions.forEach((a, j) => {
|
|
292
299
|
const i = rows.length + j;
|
|
293
|
-
lines.push({ text: actionText(a, state, i === state.cursor), rowIndex: i });
|
|
294
|
-
if (i === state.cursor) for (const note of a.notes) lines.push({ text: dim(` ${note}`) });
|
|
300
|
+
lines.push({ text: actionText(a, state, i === state.cursor, showReasons), rowIndex: i });
|
|
301
|
+
if (showReasons && i === state.cursor) for (const note of a.notes) lines.push({ text: dim(` ${note}`) });
|
|
295
302
|
});
|
|
296
303
|
}
|
|
297
304
|
const viewport = Math.max(5, height - out.length - 3);
|
|
@@ -346,16 +353,17 @@ function splitKeys(chunk) {
|
|
|
346
353
|
}
|
|
347
354
|
return keys;
|
|
348
355
|
}
|
|
349
|
-
function runChecklist(initial) {
|
|
356
|
+
function runChecklist(initial, opts = {}) {
|
|
350
357
|
const stdin = process.stdin;
|
|
351
358
|
const stdout = process.stdout;
|
|
352
359
|
return new Promise((resolve) => {
|
|
353
360
|
let state = initial;
|
|
354
361
|
let filtering = false;
|
|
355
362
|
let confirming = null;
|
|
363
|
+
let showReasons = opts.showReasons ?? false;
|
|
356
364
|
const draw = () => {
|
|
357
365
|
const height = stdout.rows || 30;
|
|
358
|
-
const body = confirming !== null ? renderNuclearConfirm(state, confirming, height) : render(state, height, filtering);
|
|
366
|
+
const body = confirming !== null ? renderNuclearConfirm(state, confirming, height) : render(state, height, filtering, showReasons);
|
|
359
367
|
stdout.write(`\x1B[H\x1B[2J${body}`);
|
|
360
368
|
};
|
|
361
369
|
const finish = (result) => {
|
|
@@ -425,6 +433,9 @@ function runChecklist(initial) {
|
|
|
425
433
|
case "/":
|
|
426
434
|
filtering = true;
|
|
427
435
|
break;
|
|
436
|
+
case "?":
|
|
437
|
+
showReasons = !showReasons;
|
|
438
|
+
break;
|
|
428
439
|
case "\x1B":
|
|
429
440
|
if (state.filter) act({ type: "filter", text: "" });
|
|
430
441
|
break;
|
|
@@ -701,8 +712,7 @@ async function describeInteractively(opts) {
|
|
|
701
712
|
const file = { version: 1, descriptions: { ...opts.file.descriptions } };
|
|
702
713
|
let changed = 0;
|
|
703
714
|
const n = opts.routes.length;
|
|
704
|
-
console.log(
|
|
705
|
-
${bold(opts.title)} \xB7 Tool descriptions ${dim("(what agents read to pick a tool)")}`);
|
|
715
|
+
console.log(opts.title);
|
|
706
716
|
if (n === 0) {
|
|
707
717
|
console.log(green(" \u2713 Every tool has a description."));
|
|
708
718
|
return { file, changed };
|
|
@@ -1423,6 +1433,8 @@ describe:
|
|
|
1423
1433
|
--ci AI descriptions for read routes only, no prompts; writes need a person
|
|
1424
1434
|
|
|
1425
1435
|
inspect:
|
|
1436
|
+
--why Show why each route is or isn't a tool (hidden by default; in
|
|
1437
|
+
the setup checklist, press ? to toggle the same reasons)
|
|
1426
1438
|
--json Print the full scan as JSON
|
|
1427
1439
|
--snapshot Write ${SNAPSHOT_FILE}: the routes agents can call. Commit it.
|
|
1428
1440
|
--check Exit 1 if the app's tools no longer match ${SNAPSHOT_FILE}, or a
|
|
@@ -1444,7 +1456,8 @@ function parseArgs(argv) {
|
|
|
1444
1456
|
help: false,
|
|
1445
1457
|
noEnvFile: false,
|
|
1446
1458
|
noSource: false,
|
|
1447
|
-
regenerate: false
|
|
1459
|
+
regenerate: false,
|
|
1460
|
+
why: false
|
|
1448
1461
|
};
|
|
1449
1462
|
for (let i = 0; i < argv.length; i++) {
|
|
1450
1463
|
const a = argv[i];
|
|
@@ -1463,6 +1476,7 @@ function parseArgs(argv) {
|
|
|
1463
1476
|
else if (a === "--api-url") args.apiUrl = value();
|
|
1464
1477
|
else if (a === "--no-source") args.noSource = true;
|
|
1465
1478
|
else if (a === "--regenerate") args.regenerate = true;
|
|
1479
|
+
else if (a === "--why") args.why = true;
|
|
1466
1480
|
else if (a === "--file") args.file = value();
|
|
1467
1481
|
else if (a === "--env-vars") args.envFile = value();
|
|
1468
1482
|
else if (a === "--no-env-vars") args.noEnvFile = true;
|
|
@@ -1565,37 +1579,50 @@ ${tail.replace(/^/gm, " ")}`));
|
|
|
1565
1579
|
}
|
|
1566
1580
|
return { result, root, registered, env, location };
|
|
1567
1581
|
}
|
|
1568
|
-
function inspect(result, cwd) {
|
|
1582
|
+
function inspect(result, cwd, why) {
|
|
1569
1583
|
const opts = result.aiReady?.options;
|
|
1570
1584
|
const where = result.aiReady?.site ? `at ${relative(cwd, result.aiReady.site.file)}:${result.aiReady.site.line}` : "";
|
|
1571
1585
|
console.log(result.aiReady ? `aiReady() ${where}` : yellow("aiReady() isn't mounted yet: showing the defaults (reads exposed, writes hidden)."));
|
|
1572
1586
|
if (opts?.expose === "*") console.log(red(' expose: "*" -- every write route is exposed, including ones added later'));
|
|
1573
1587
|
if (opts) console.log(dim(` startup log: ${opts.startupLog === false ? "off" : "on"}`));
|
|
1574
|
-
console.log("");
|
|
1575
1588
|
if (result.routes.length === 0 && result.actions?.length) {
|
|
1589
|
+
console.log("");
|
|
1576
1590
|
console.log(dim(" No route handlers."));
|
|
1577
|
-
inspectActions(result, cwd);
|
|
1578
|
-
inspectLogin(result);
|
|
1591
|
+
inspectActions(result, cwd, why);
|
|
1592
|
+
inspectLogin(result, why);
|
|
1579
1593
|
return;
|
|
1580
1594
|
}
|
|
1581
1595
|
const toolWidth = Math.max(4, ...result.routes.map((r) => r.tool.length));
|
|
1582
1596
|
const routeWidth = Math.max(5, ...result.routes.map((r) => r.method.length + 1 + r.path.length));
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
}
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1597
|
+
const exposed = result.routes.filter((r) => r.exposed);
|
|
1598
|
+
const hidden = result.routes.filter((r) => !r.exposed);
|
|
1599
|
+
console.log(`
|
|
1600
|
+
${bold("Tools")} ${dim(`(${exposed.length})`)}`);
|
|
1601
|
+
if (exposed.length === 0) console.log(dim(" none exposed"));
|
|
1602
|
+
else {
|
|
1603
|
+
console.log(dim(` ${"TOOL".padEnd(toolWidth)} ${"ROUTE".padEnd(routeWidth)} ${"KIND".padEnd(5)}${why ? " WHY" : ""}`.trimEnd()));
|
|
1604
|
+
for (const r of exposed) {
|
|
1605
|
+
const cell = (r.kind === "page" ? "page" : isRead(r) ? "read" : "write").padEnd(5);
|
|
1606
|
+
const kind = r.kind === "page" ? dim(cell) : isRead(r) ? cell : yellow(cell);
|
|
1607
|
+
console.log(` ${r.tool.padEnd(toolWidth)} ${`${r.method} ${r.path}`.padEnd(routeWidth)} ${kind}${why ? ` ${dim(r.reason)}` : ""}`.trimEnd());
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
if (hidden.length > 0) {
|
|
1611
|
+
const hint = why ? "" : dim(` ${adapter.bin} inspect --why for why each is hidden`);
|
|
1612
|
+
console.log(`
|
|
1613
|
+
${bold("Hidden")} ${dim(`(${hidden.length})`)}${hint}`);
|
|
1614
|
+
for (const r of hidden) {
|
|
1615
|
+
console.log(` ${dim(r.tool.padEnd(toolWidth))} ${`${r.method} ${r.path}`.padEnd(routeWidth)}${why ? ` ${dim(r.reason)}` : ""}`.trimEnd());
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
const writes = exposed.filter((r) => !isRead(r)).length;
|
|
1619
|
+
const pageTools = exposed.filter((r) => r.kind === "page").length;
|
|
1620
|
+
const routeTools = exposed.length - pageTools;
|
|
1594
1621
|
const counts = [`${routeTools} route tool${routeTools === 1 ? "" : "s"} (${routeTools - writes} read, ${writes} write)`, ...pageTools > 0 ? [`${pageTools} page tool${pageTools === 1 ? "" : "s"}`] : []];
|
|
1595
1622
|
console.log(`
|
|
1596
1623
|
${counts.join(", ")}, plus search_docs when docs are found.`);
|
|
1597
|
-
if (result.actions && result.actions.length > 0) inspectActions(result, cwd);
|
|
1598
|
-
inspectLogin(result);
|
|
1624
|
+
if (result.actions && result.actions.length > 0) inspectActions(result, cwd, why);
|
|
1625
|
+
inspectLogin(result, why);
|
|
1599
1626
|
}
|
|
1600
1627
|
function supabaseSignIn(s, login) {
|
|
1601
1628
|
if (s.authHelpers) return { row: "none: @supabase/auth-helpers-nextjs isn't supported", why: ["It keeps the session in an older cookie format. Move to @supabase/ssr, Supabase's replacement for it."] };
|
|
@@ -1619,36 +1646,41 @@ function supabaseSignIn(s, login) {
|
|
|
1619
1646
|
if (s.callbacks?.length) why.push(`Connect's sign-ins don't go through ${s.callbacks.join(", ")}; anything those check is skipped.`);
|
|
1620
1647
|
return { row, why };
|
|
1621
1648
|
}
|
|
1622
|
-
function inspectLogin(result) {
|
|
1649
|
+
function inspectLogin(result, showWhy) {
|
|
1623
1650
|
if (result.supabase) {
|
|
1624
1651
|
const { row, why } = supabaseSignIn(result.supabase, result.login);
|
|
1625
1652
|
console.log(`
|
|
1626
1653
|
Sign-in for AI assistants: ${row}.`);
|
|
1627
|
-
|
|
1628
|
-
|
|
1654
|
+
if (showWhy) {
|
|
1655
|
+
for (const line of why) console.log(dim(` ${line}`));
|
|
1656
|
+
if (result.supabase.found.length > 1) for (const f of result.supabase.found) console.log(dim(` found ${f}`));
|
|
1657
|
+
}
|
|
1629
1658
|
if (result.supabase.direct.length && !result.supabase.authHelpers) console.log(dim(" Check it on a running app: npx @exposify/next test-sign-in"));
|
|
1630
1659
|
return;
|
|
1631
1660
|
}
|
|
1632
1661
|
if (!result.login) return;
|
|
1633
1662
|
console.log(`
|
|
1634
1663
|
Sign-in for AI assistants: your app's own login, ${result.login.what} (${result.login.fields.join(", ")}).`);
|
|
1635
|
-
|
|
1636
|
-
|
|
1664
|
+
if (showWhy) {
|
|
1665
|
+
console.log(dim(` With ${KEY_ENV} set, they sign in on a form your app serves at /mcp/login; passwords stay on your app.`));
|
|
1666
|
+
if (result.login.others?.length) console.log(dim(` Also looked like a login: ${result.login.others.join(", ")}. Pick another with aiReady({ login }).`));
|
|
1667
|
+
}
|
|
1637
1668
|
}
|
|
1638
1669
|
var STATUS_TEXT = { complete: "input known", partial: "input partly unknown", flagged: "flagged" };
|
|
1639
|
-
function inspectActions(result, _cwd) {
|
|
1670
|
+
function inspectActions(result, _cwd, why) {
|
|
1640
1671
|
const actions = result.actions;
|
|
1641
1672
|
console.log(`
|
|
1642
1673
|
${bold("Server Actions")}`);
|
|
1643
1674
|
const toolWidth = Math.max(4, ...actions.map((a) => a.tool.length));
|
|
1644
1675
|
const whereWidth = Math.max(6, ...actions.map((a) => `${a.export} ${a.file}:${a.line}`.length));
|
|
1645
|
-
console.log(dim(` ${"TOOL".padEnd(toolWidth)} ${"ACTION".padEnd(whereWidth)} ${"TOOL?".padEnd(5)} WHY`));
|
|
1676
|
+
console.log(dim(` ${"TOOL".padEnd(toolWidth)} ${"ACTION".padEnd(whereWidth)} ${"TOOL?".padEnd(5)}${why ? " WHY" : ""}`));
|
|
1646
1677
|
for (const a of actions) {
|
|
1647
1678
|
const tool = a.tool.padEnd(toolWidth);
|
|
1648
1679
|
const where = `${a.export} ${dim(`${a.file}:${a.line}`)}${" ".repeat(Math.max(0, whereWidth - `${a.export} ${a.file}:${a.line}`.length))}`;
|
|
1649
1680
|
const status = a.status === "complete" ? dim(STATUS_TEXT[a.status]) : yellow(STATUS_TEXT[a.status]);
|
|
1650
|
-
|
|
1651
|
-
|
|
1681
|
+
const mark = a.exposed ? green("yes ") : dim("no ");
|
|
1682
|
+
console.log(` ${a.exposed ? tool : dim(tool)} ${where} ${mark}${why ? ` ${dim(a.reason)} \xB7 ${status}` : ""}`.trimEnd());
|
|
1683
|
+
if (why) for (const note of a.notes) console.log(dim(` ${note}`));
|
|
1652
1684
|
for (const d of a.drift) console.log(yellow(` ! ${d}`));
|
|
1653
1685
|
}
|
|
1654
1686
|
const exposed = actions.filter((a) => a.exposed).length;
|
|
@@ -1730,6 +1762,7 @@ async function setup(args, cwd) {
|
|
|
1730
1762
|
return 1;
|
|
1731
1763
|
}
|
|
1732
1764
|
const tty = interactive() && !args.ci;
|
|
1765
|
+
const guided = tty && !args.quick;
|
|
1733
1766
|
const selectionFromCli = args.ci || args.quick || args.exposeAll || args.expose.length > 0 || args.actions.length > 0;
|
|
1734
1767
|
const current = result.aiReady?.options;
|
|
1735
1768
|
const currentNuclear = current?.expose === "*";
|
|
@@ -1737,12 +1770,15 @@ async function setup(args, cwd) {
|
|
|
1737
1770
|
console.error("Not a terminal: pass --ci to accept the defaults (reads only), or --expose / --expose-all.");
|
|
1738
1771
|
return 1;
|
|
1739
1772
|
}
|
|
1773
|
+
if (guided) console.log(`
|
|
1774
|
+
${bold(`Setting up ${appNameOf(root)}`)} ${dim("\xB7 endpoints \u2192 startup log \u2192 config \u2192 sign-in \u2192 descriptions")}`);
|
|
1740
1775
|
let selected;
|
|
1741
1776
|
let nuclear;
|
|
1742
1777
|
let checklistActions;
|
|
1743
1778
|
if (tty && !selectionFromCli) {
|
|
1744
1779
|
const final = await runChecklist(
|
|
1745
|
-
initialState(result.routes, { hasConfig: Boolean(result.aiReady), nuclear: currentNuclear, actions: result.actions ?? [], listedActions: current?.actions ?? [] })
|
|
1780
|
+
initialState(result.routes, { hasConfig: Boolean(result.aiReady), nuclear: currentNuclear, actions: result.actions ?? [], listedActions: current?.actions ?? [] }),
|
|
1781
|
+
{ showReasons: args.why }
|
|
1746
1782
|
);
|
|
1747
1783
|
if (!final) {
|
|
1748
1784
|
console.log("Cancelled.");
|
|
@@ -1765,11 +1801,9 @@ async function setup(args, cwd) {
|
|
|
1765
1801
|
const pickedWrites = picked.filter((r) => !isRead(r)).length;
|
|
1766
1802
|
if (result.routes.length > 0) {
|
|
1767
1803
|
console.log(
|
|
1768
|
-
`
|
|
1769
|
-
${bold("Step 1/5")} \xB7 ${picked.length} tool${picked.length === 1 ? "" : "s"} (${picked.length - pickedWrites} read, ${pickedWrites} write)` + (nuclear ? red(' -- expose: "*": all write routes, including future ones') : "")
|
|
1804
|
+
stepHead(guided, 1, 5, `${picked.length} tool${picked.length === 1 ? "" : "s"} (${picked.length - pickedWrites} read, ${pickedWrites} write)`) + (nuclear ? red(' -- expose: "*": all write routes, including future ones') : "")
|
|
1770
1805
|
);
|
|
1771
|
-
} else console.log(
|
|
1772
|
-
${bold("Step 1/5")} \xB7 Server Actions`);
|
|
1806
|
+
} else console.log(stepHead(guided, 1, 5, "Server Actions"));
|
|
1773
1807
|
if (selectionFromCli && currentNuclear && !nuclear) {
|
|
1774
1808
|
console.log(yellow(' Your config has expose: "*". This writes it out as an explicit list, so write routes added later'));
|
|
1775
1809
|
console.log(yellow(` won't be exposed automatically. Pass --expose-all to keep "*".`));
|
|
@@ -1793,8 +1827,7 @@ ${bold("Step 1/5")} \xB7 Server Actions`);
|
|
|
1793
1827
|
}
|
|
1794
1828
|
let startupLog = args.startupLog ?? current?.startupLog ?? true;
|
|
1795
1829
|
if (tty && !args.quick && args.startupLog === void 0) {
|
|
1796
|
-
console.log(
|
|
1797
|
-
${bold("Step 2/5")} \xB7 Print "[agentready] MCP ready \u2026" when your server starts?`);
|
|
1830
|
+
console.log(stepHead(guided, 2, 5, 'Print "[agentready] MCP ready \u2026" when your server starts?'));
|
|
1798
1831
|
console.log(dim(" It shows the tool count and auth mode. Failures always print."));
|
|
1799
1832
|
const answer = (await ask(` ${startupLog ? "(Y/n)" : "(y/N)"} `)).toLowerCase();
|
|
1800
1833
|
if (answer === "y" || answer === "yes") startupLog = true;
|
|
@@ -1817,8 +1850,8 @@ ${bold("Step 2/5")} \xB7 Print "[agentready] MCP ready \u2026" when your server
|
|
|
1817
1850
|
console.log(`
|
|
1818
1851
|
${green("\u2713 Already set up")}: your aiReady() config already matches. Nothing to change.`);
|
|
1819
1852
|
if (tty && !args.quick) {
|
|
1820
|
-
await signInStep(args, root, cwd,
|
|
1821
|
-
const code2 = await describeStep(args, toolRows(result.routes, result.actions), root, cwd,
|
|
1853
|
+
await signInStep(args, root, cwd, stepHead(guided, 4, 5, "How your users sign in"), result.login, result.supabase);
|
|
1854
|
+
const code2 = await describeStep(args, toolRows(result.routes, result.actions), root, cwd, describeHeader(guided));
|
|
1822
1855
|
if (code2 !== 0) return code2;
|
|
1823
1856
|
}
|
|
1824
1857
|
await offerSnapshot(args, tty, toolRows(result.routes, result.actions), root, cwd);
|
|
@@ -1828,8 +1861,7 @@ ${green("\u2713 Already set up")}: your aiReady() config already matches. Nothin
|
|
|
1828
1861
|
const shouldCopy = args.copy ?? tty;
|
|
1829
1862
|
const copied = shouldCopy ? copyToClipboard(code) : null;
|
|
1830
1863
|
const copyNote = copied?.certain ? green(" (copied to clipboard \u2713)") : copied ? dim(" (sent to your terminal's clipboard, if it supports that)") : "";
|
|
1831
|
-
console.log(
|
|
1832
|
-
${bold("Step 3/5")} \xB7 Your aiReady() config${copyNote}
|
|
1864
|
+
console.log(`${stepHead(guided, 3, 5, "Your aiReady() config")}${copyNote}
|
|
1833
1865
|
`);
|
|
1834
1866
|
console.log(code.replace(/^/gm, " "));
|
|
1835
1867
|
let applied = false;
|
|
@@ -1915,8 +1947,8 @@ ${bold("Step 3/5")} \xB7 Your aiReady() config${copyNote}
|
|
|
1915
1947
|
}
|
|
1916
1948
|
const finalRoutes = verified ?? toolRows(routesAsSelected(registered, selected), actionRows.map((a) => ({ ...a, exposed: a.marker ? a.exposed : pickedActions.has(a.configKey) })));
|
|
1917
1949
|
if (tty && !args.quick) {
|
|
1918
|
-
await signInStep(args, root, cwd,
|
|
1919
|
-
const code2 = await describeStep(args, finalRoutes, root, cwd,
|
|
1950
|
+
await signInStep(args, root, cwd, stepHead(guided, 4, 5, "How your users sign in"), result.login, result.supabase);
|
|
1951
|
+
const code2 = await describeStep(args, finalRoutes, root, cwd, describeHeader(guided));
|
|
1920
1952
|
if (code2 !== 0) return code2;
|
|
1921
1953
|
const proposed = await proposeStep(args, verifiedActions, root, cwd);
|
|
1922
1954
|
if (proposed !== 0) return proposed;
|
|
@@ -1925,6 +1957,9 @@ ${bold("Step 3/5")} \xB7 Your aiReady() config${copyNote}
|
|
|
1925
1957
|
Descriptions: run \`${adapter.bin} describe\` to replace the auto-derived ones.`));
|
|
1926
1958
|
}
|
|
1927
1959
|
await offerSnapshot(args, tty, finalRoutes, root, cwd);
|
|
1960
|
+
const toolCount = finalRoutes.filter((r) => r.exposed).length;
|
|
1961
|
+
if (guided) console.log(`
|
|
1962
|
+
${green("\u2713 Done")} \u2014 agents can call ${bold(String(toolCount))} tool${toolCount === 1 ? "" : "s"}.`);
|
|
1928
1963
|
console.log(`
|
|
1929
1964
|
${dim("Next:")} run ${cyan(`${adapter.bin} inspect`)} any time to see what agents get.`);
|
|
1930
1965
|
return 0;
|
|
@@ -1960,12 +1995,11 @@ ${bold("Optional")} \xB7 Save the tools agents get to ${shown}?`);
|
|
|
1960
1995
|
}
|
|
1961
1996
|
if (save) writeSnapshot(routes, file, cwd);
|
|
1962
1997
|
}
|
|
1963
|
-
async function signInStep(args, root, cwd,
|
|
1998
|
+
async function signInStep(args, root, cwd, header2, login, supabase) {
|
|
1964
1999
|
if (!args.signIn) return;
|
|
1965
2000
|
const stacks = detectAuthStacks(root);
|
|
1966
2001
|
const oauth = stacks.find((s) => s.path === "oauth");
|
|
1967
|
-
console.log(
|
|
1968
|
-
${bold(title)} \xB7 How your users sign in`);
|
|
2002
|
+
console.log(header2);
|
|
1969
2003
|
if (stacks.length > 0) console.log(dim(` Found ${stacks.map((s) => `${s.label} (${s.pkg})`).join(", ")} in package.json.`));
|
|
1970
2004
|
if (oauth) {
|
|
1971
2005
|
console.log(` ${oauth.label} can sign agents in itself: MCP clients log in to it directly, so no Exposify broker is needed.`);
|
|
@@ -2031,7 +2065,10 @@ function serviceConfig(args) {
|
|
|
2031
2065
|
const key = resolveLicenseKey(args.licenseKey);
|
|
2032
2066
|
return { apiUrl, ...key ? { licenseKey: key.key } : {} };
|
|
2033
2067
|
}
|
|
2034
|
-
|
|
2068
|
+
function describeHeader(guided) {
|
|
2069
|
+
return `${stepHead(guided, 5, 5, "Tool descriptions")} ${dim("(what agents read to pick a tool)")}`;
|
|
2070
|
+
}
|
|
2071
|
+
async function describeStep(args, routes, root, cwd, header2) {
|
|
2035
2072
|
const path = join4(root, DESCRIPTIONS_FILE);
|
|
2036
2073
|
const file = readDescriptionsFile(path);
|
|
2037
2074
|
if (typeof file === "string") {
|
|
@@ -2051,7 +2088,7 @@ async function describeStep(args, routes, root, cwd, title) {
|
|
|
2051
2088
|
file,
|
|
2052
2089
|
service,
|
|
2053
2090
|
includeSource: !args.noSource,
|
|
2054
|
-
title
|
|
2091
|
+
title: header2
|
|
2055
2092
|
});
|
|
2056
2093
|
if (result.changed > 0) {
|
|
2057
2094
|
writeDescriptionsFile(path, result.file);
|
|
@@ -2196,7 +2233,7 @@ async function describeCommand(args, cwd) {
|
|
|
2196
2233
|
console.error("Not a terminal: pass --ci (reads only) or --check.");
|
|
2197
2234
|
return 1;
|
|
2198
2235
|
}
|
|
2199
|
-
const code = await describeStep(args, exposed, root, cwd,
|
|
2236
|
+
const code = await describeStep(args, exposed, root, cwd, describeHeader(false));
|
|
2200
2237
|
if (code !== 0) return code;
|
|
2201
2238
|
return proposeStep(args, result.actions ?? [], root, cwd);
|
|
2202
2239
|
}
|
|
@@ -2260,7 +2297,7 @@ async function runCli(argv, forAdapter, cwd = process.cwd()) {
|
|
|
2260
2297
|
if (args.snapshot) return writeSnapshot(toolRows(scanned.result.routes, scanned.result.actions), file, cwd);
|
|
2261
2298
|
if (args.check) return Math.max(checkSnapshot(scanned.result, file, cwd), checkDrift(scanned.result));
|
|
2262
2299
|
if (args.json) console.log(JSON.stringify(scanned.result, null, 2));
|
|
2263
|
-
else inspect(scanned.result, scanned.root);
|
|
2300
|
+
else inspect(scanned.result, scanned.root, args.why);
|
|
2264
2301
|
return 0;
|
|
2265
2302
|
}
|
|
2266
2303
|
if (args.command === "setup") return setup(args, cwd);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exposify/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@babel/parser": "^8.0.0",
|
|
25
|
-
"@exposify/core": "0.3.
|
|
25
|
+
"@exposify/core": "0.3.18"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^22.0.0",
|