@exposify/cli 0.0.3 → 0.0.5

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.d.ts CHANGED
@@ -23,6 +23,8 @@ interface ScannedRoute {
23
23
  exposed: boolean;
24
24
  /** Exposed only when listed in `expose`, even a read (core's RouteInfo.optIn), e.g. a Pages Router method detected in code. */
25
25
  optIn?: boolean;
26
+ /** Not a tool unless listed in `expose`, so setup leaves it unticked though it's a read: a page that's just a form, a route that finishes a sign-in. */
27
+ offByDefault?: boolean;
26
28
  /** "page": an App Router page read as a tool (its HTML, like a browser), not a route handler. */
27
29
  kind?: "page";
28
30
  reason: string;
@@ -215,8 +217,13 @@ interface ApplyPlan {
215
217
  after: string;
216
218
  }
217
219
  /** Either a plan, or why this one can't be applied safely (setup then prints the paste instructions). */
220
+ /** `also`: edits to other files that go with the plan, each with what to do by hand when it isn't written. */
218
221
  type ApplyOutcome = {
219
222
  plan: ApplyPlan;
223
+ also?: Array<{
224
+ plan: ApplyPlan;
225
+ manual: string;
226
+ }>;
220
227
  } | {
221
228
  blocked: string;
222
229
  };
@@ -300,6 +307,8 @@ interface CliAdapter<R extends ScanResult = ScanResult> {
300
307
  entryExample: string;
301
308
  /** What `--entry` names, for the usage text, e.g. "The file that creates your Express app". */
302
309
  entryHelp: string;
310
+ /** The adapter's own commands, for the usage text: ["generate", "Writes …"]. Its bin runs them, not runCli. */
311
+ commands?: Array<[name: string, help: string]>;
303
312
  locate(cwd: string, entryFlag?: string): AppLocation | undefined;
304
313
  scan(location: AppLocation, env: Record<string, string>): Promise<AdapterScanOutcome<R>>;
305
314
  /**
package/dist/index.js CHANGED
@@ -47,6 +47,7 @@ import { DESCRIPTIONS_FILE, parseDescriptions } from "@exposify/core";
47
47
  // src/checklist.ts
48
48
  var routeKey = (r) => `${r.method} ${r.path}`;
49
49
  var isRead = (r) => r.method === "GET" || r.method === "HEAD";
50
+ var readByDefault = (r) => isRead(r) && !r.offByDefault;
50
51
  function isLocked(r) {
51
52
  return r.marker !== void 0;
52
53
  }
@@ -55,10 +56,10 @@ function initialState(routes, opts) {
55
56
  const listed = new Set(opts.listedActions ?? []);
56
57
  const selected = /* @__PURE__ */ new Set();
57
58
  for (const r of routes) {
58
- const ticked = opts.hasConfig || isLocked(r) ? r.exposed : isRead(r);
59
+ const ticked = opts.hasConfig || isLocked(r) ? r.exposed : readByDefault(r);
59
60
  if (ticked) selected.add(routeKey(r));
60
61
  }
61
- const readsOnly = new Set(routes.filter((r) => isLocked(r) ? r.exposed : isRead(r)).map(routeKey));
62
+ const readsOnly = new Set(routes.filter((r) => isLocked(r) ? r.exposed : readByDefault(r)).map(routeKey));
62
63
  return {
63
64
  routes,
64
65
  selected,
@@ -130,7 +131,7 @@ function reduce(state, action) {
130
131
  case "none":
131
132
  return setTicks(state, () => false, true);
132
133
  case "readsOnly":
133
- return setTicks(state, isRead, true);
134
+ return setTicks(state, readByDefault, true);
134
135
  case "filter":
135
136
  return { ...state, filter: action.text, cursor: 0 };
136
137
  case "enterNuclear": {
@@ -1345,14 +1346,18 @@ function openInBrowser(url) {
1345
1346
  }
1346
1347
 
1347
1348
  // src/run.ts
1348
- var usage = (bin, entryHelp) => `Usage: ${bin} <command> [options]
1349
+ var usage = ({ bin, entryHelp, commands: commands2 = [] }) => `Usage: ${bin} <command> [options]
1349
1350
 
1350
1351
  Commands:
1351
1352
  setup Pick which endpoints agents can call, then get the aiReady() config to paste
1352
1353
  inspect Show what agents see: every route, whether it's a tool, and why
1353
1354
  describe Give tools real descriptions (AI-proposed and reviewed, or your own) in
1354
1355
  ${DESCRIPTIONS_FILE}, which aiReady() loads
1355
-
1356
+ ${commands2.map(([name, help]) => name.length > 8 ? ` ${name}
1357
+ ${help}
1358
+ ` : ` ${name.padEnd(8)} ${help}
1359
+ `).join("")}${commands2.length ? ` (${bin} <command> --help for its options)
1360
+ ` : ""}
1356
1361
  Options:
1357
1362
  --entry <path> ${entryHelp} (default: detected)
1358
1363
  --env-vars <path> Load these variables into the scan (setup asks about .env otherwise)
@@ -1775,7 +1780,9 @@ ${bold("Step 2/5")} \xB7 Print "[agentready] MCP ready \u2026" when your server
1775
1780
  }
1776
1781
  const alreadyExposed = new Set(result.routes.filter((r) => r.exposed).map(routeKey));
1777
1782
  const unchanged = result.aiReady && differences(result.routes, selected, alreadyExposed).length === 0 && (current?.startupLog ?? true) === startupLog && currentNuclear === nuclear && sameActions(current?.actions);
1778
- if (unchanged) {
1783
+ const step = adapter.configStep({ result, config, current, root, readLine: readLineAt });
1784
+ const pendingAlso = step.apply && "plan" in step.apply ? step.apply.also?.length ?? 0 : 0;
1785
+ if (unchanged && pendingAlso === 0) {
1779
1786
  console.log(`
1780
1787
  ${green("\u2713 Already set up")}: your aiReady() config already matches. Nothing to change.`);
1781
1788
  if (tty && !args.quick) {
@@ -1786,7 +1793,7 @@ ${green("\u2713 Already set up")}: your aiReady() config already matches. Nothin
1786
1793
  await offerSnapshot(args, tty, toolRows(result.routes, result.actions), root, cwd);
1787
1794
  return 0;
1788
1795
  }
1789
- const { code, hint, apply } = adapter.configStep({ result, config, current, root, readLine: readLineAt });
1796
+ const { code, hint, apply } = step;
1790
1797
  const shouldCopy = args.copy ?? tty;
1791
1798
  const copied = shouldCopy ? copyToClipboard(code) : null;
1792
1799
  const copyNote = copied?.certain ? green(" (copied to clipboard \u2713)") : copied ? dim(" (sent to your terminal's clipboard, if it supports that)") : "";
@@ -1795,6 +1802,7 @@ ${bold("Step 3/5")} \xB7 Your aiReady() config${copyNote}
1795
1802
  `);
1796
1803
  console.log(code.replace(/^/gm, " "));
1797
1804
  let applied = false;
1805
+ let manual = apply && "plan" in apply ? (apply.also ?? []).map((a) => a.manual) : [];
1798
1806
  if (args.apply !== false && (args.apply || tty && !args.quick)) {
1799
1807
  if (!apply) {
1800
1808
  if (args.apply) console.log(yellow("\n Can't apply automatically here: paste it as described below."));
@@ -1803,20 +1811,31 @@ ${bold("Step 3/5")} \xB7 Your aiReady() config${copyNote}
1803
1811
  Can't apply automatically: ${apply.blocked}. Paste it as described below.`));
1804
1812
  } else {
1805
1813
  const { summary } = apply.plan;
1814
+ const also = apply.also ?? [];
1815
+ const all = [summary, ...also.map((a) => a.plan.summary.replace(/^./, (c) => c.toLowerCase()))];
1806
1816
  const yes = args.apply === true || ["y", "yes"].includes((await ask(`
1807
- Apply this: ${summary}? ${dim("(y/N)")} `)).toLowerCase());
1817
+ Apply this: ${all.join(", and ")}? ${dim("(y/N)")} `)).toLowerCase());
1808
1818
  if (yes) {
1809
1819
  const written = writePlan(apply.plan);
1810
1820
  if (written.ok) {
1811
1821
  applied = true;
1812
1822
  console.log(green(` \u2713 Applied: ${summary}.`) + dim(apply.plan.before === null ? "" : " Review it with git diff."));
1823
+ manual = [];
1824
+ for (const a of also) {
1825
+ const done = writePlan(a.plan);
1826
+ if (done.ok) console.log(green(` \u2713 Applied: ${a.plan.summary}.`) + dim(" Review it with git diff."));
1827
+ else {
1828
+ console.log(yellow(` Couldn't apply: ${a.plan.summary} (${done.error}).`));
1829
+ manual.push(a.manual);
1830
+ }
1831
+ }
1813
1832
  } else console.log(yellow(` Couldn't apply: ${written.error}. Paste it as described below.`));
1814
1833
  }
1815
1834
  }
1816
1835
  }
1817
1836
  if (!applied) console.log(`
1818
1837
  ${hint.lines[0]}`);
1819
- for (const line of hint.lines.slice(1)) console.log(` ${line}`);
1838
+ for (const line of [...hint.lines.slice(1), ...manual]) console.log(` ${line}`);
1820
1839
  if (hint.warning) console.log(yellow(` ! ${hint.warning}`));
1821
1840
  if (droppedGlobs.length > 0) {
1822
1841
  console.log(yellow(` ! Dropped exclude ${droppedGlobs.map((g) => `"${g}"`).join(", ")}: it would hide endpoints you ticked.`));
@@ -2190,11 +2209,11 @@ async function runCli(argv, forAdapter, cwd = process.cwd()) {
2190
2209
  args = parseArgs(argv);
2191
2210
  } catch (err) {
2192
2211
  console.error(red(err.message));
2193
- console.error(usage(adapter.bin, adapter.entryHelp));
2212
+ console.error(usage(adapter));
2194
2213
  return 2;
2195
2214
  }
2196
2215
  if (args.help || !args.command) {
2197
- console.log(usage(adapter.bin, adapter.entryHelp));
2216
+ console.log(usage(adapter));
2198
2217
  return args.help ? 0 : 2;
2199
2218
  }
2200
2219
  if (args.ci) disableColor();
@@ -2215,7 +2234,7 @@ async function runCli(argv, forAdapter, cwd = process.cwd()) {
2215
2234
  if (args.command === "setup") return setup(args, cwd);
2216
2235
  if (args.command === "describe") return describeCommand(args, cwd);
2217
2236
  console.error(red(`unknown command: ${args.command}`));
2218
- console.error(usage(adapter.bin, adapter.entryHelp));
2237
+ console.error(usage(adapter));
2219
2238
  return 2;
2220
2239
  }
2221
2240
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exposify/cli",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",