@exposify/cli 0.0.6 → 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.
Files changed (2) hide show
  1. package/dist/index.js +100 -63
  2. 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("Step 1/5 \xB7 Which endpoints should agents be able to call?"));
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(" Reads are pre-selected. space toggle \xB7 a all \xB7 n none \xB7 r reads only \xB7 / filter"));
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
- console.log(dim(` ${"TOOL".padEnd(toolWidth)} ${"ROUTE".padEnd(routeWidth)} ${"TOOL?".padEnd(5)} WHY`));
1584
- for (const r of result.routes) {
1585
- const mark = r.exposed ? green("yes ") : dim("no ");
1586
- const route = `${r.method} ${r.path}`.padEnd(routeWidth);
1587
- const tool = r.tool.padEnd(toolWidth);
1588
- console.log(` ${r.exposed ? tool : dim(tool)} ${route} ${mark} ${dim(r.reason)}`);
1589
- }
1590
- const tools = result.routes.filter((r) => r.exposed);
1591
- const writes = tools.filter((r) => !isRead(r)).length;
1592
- const pageTools = tools.filter((r) => r.kind === "page").length;
1593
- const routeTools = tools.length - pageTools;
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
- for (const line of why) console.log(dim(` ${line}`));
1628
- if (result.supabase.found.length > 1) for (const f of result.supabase.found) console.log(dim(` found ${f}`));
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
- console.log(dim(` With ${KEY_ENV} set, they sign in on a form your app serves at /mcp/login; passwords stay on your app.`));
1636
- if (result.login.others?.length) console.log(dim(` Also looked like a login: ${result.login.others.join(", ")}. Pick another with aiReady({ login }).`));
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
- console.log(` ${a.exposed ? tool : dim(tool)} ${where} ${a.exposed ? green("yes ") : dim("no ")} ${dim(a.reason)} \xB7 ${status}`);
1651
- for (const note of a.notes) console.log(dim(` ${note}`));
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, "Step 4/5", result.login, result.supabase);
1821
- const code2 = await describeStep(args, toolRows(result.routes, result.actions), root, cwd, "Step 5/5");
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, "Step 4/5", result.login, result.supabase);
1919
- const code2 = await describeStep(args, finalRoutes, root, cwd, "Step 5/5");
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, title, login, supabase) {
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
- async function describeStep(args, routes, root, cwd, title) {
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, "Describe");
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.6",
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.16"
25
+ "@exposify/core": "0.3.18"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/node": "^22.0.0",