@exposify/cli 0.0.1 → 0.1.0

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @exposify/cli
2
2
 
3
- The framework-neutral engine behind `exposify-express` and `exposify-next`:
3
+ The framework-neutral engine behind `@exposify/express` and `@exposify/next`:
4
4
  the endpoint checklist, the startup-log question, config + clipboard + verify,
5
5
  tool descriptions (`describe`), and `inspect` snapshots. You don't use it
6
6
  directly: install an adapter (`@exposify/express`, `@exposify/nestjs`,
package/dist/index.d.ts CHANGED
@@ -132,6 +132,22 @@ interface ScanResult {
132
132
  fields: string[];
133
133
  others?: string[];
134
134
  };
135
+ /**
136
+ * Supabase Auth with `@supabase/ssr` (Next.js): which sign-ins Connect
137
+ * makes on Supabase directly, and where the app signs in (plan
138
+ * `2026-10-24-supabase-connect.md` Part 2).
139
+ */
140
+ supabase?: {
141
+ direct: Array<"password" | "email-code" | "social">;
142
+ /** The sign-in calls found, one line each; empty: none found. */
143
+ found: string[];
144
+ /** Server sign-ins the form can't run. */
145
+ unusable?: string[];
146
+ /** Routes that finish a sign-in; Connect's sign-ins skip them. */
147
+ callbacks?: string[];
148
+ /** `@supabase/auth-helpers-nextjs`: an older cookie format, not supported. */
149
+ authHelpers?: boolean;
150
+ };
135
151
  }
136
152
  interface ScanFailure {
137
153
  ok: false;
@@ -278,7 +294,7 @@ interface ConfigStepInput<R extends ScanResult = ScanResult> {
278
294
  readLine: (site: CallSite) => string | undefined;
279
295
  }
280
296
  interface CliAdapter<R extends ScanResult = ScanResult> {
281
- /** The command name, e.g. "exposify-express". */
297
+ /** The command as users type it in hints, e.g. "npx @exposify/next". */
282
298
  bin: string;
283
299
  /** An example for "pass it explicitly", e.g. "src/server.ts". */
284
300
  entryExample: string;
package/dist/index.js CHANGED
@@ -1234,36 +1234,14 @@ import { spawn } from "child_process";
1234
1234
  import { existsSync as existsSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
1235
1235
  import { join as join3 } from "path";
1236
1236
  import { parseEnv as parseEnv2 } from "util";
1237
+ import { authStacksOf, dependencyNames } from "@exposify/core";
1237
1238
  var KEY_ENV = "EXPOSIFY_KEY";
1238
- var KNOWN = [
1239
- { test: (p) => p.startsWith("@auth0/") || p === "auth0" || p === "express-openid-connect", label: "Auth0", path: "oauth" },
1240
- { test: (p) => p.startsWith("@clerk/"), label: "Clerk", path: "oauth" },
1241
- { test: (p) => p.startsWith("@workos-inc/"), label: "WorkOS", path: "oauth" },
1242
- { test: (p) => p.startsWith("@kinde-oss/"), label: "Kinde", path: "oauth" },
1243
- { test: (p) => p === "@supabase/supabase-js" || p === "@supabase/ssr", label: "Supabase Auth", path: "broker" },
1244
- { test: (p) => p === "firebase" || p === "firebase-admin", label: "Firebase Auth", path: "broker" },
1245
- { test: (p) => p === "next-auth" || p === "@auth/core", label: "Auth.js (NextAuth)", path: "broker" },
1246
- { test: (p) => p === "better-auth", label: "Better Auth", path: "broker" },
1247
- { test: (p) => p === "lucia", label: "Lucia", path: "broker" },
1248
- { test: (p) => p === "passport", label: "Passport", path: "broker" },
1249
- { test: (p) => p === "iron-session", label: "iron-session", path: "broker" },
1250
- { test: (p) => p === "express-session" || p === "cookie-session", label: "server sessions", path: "broker" },
1251
- { test: (p) => p === "jsonwebtoken" || p === "jose" || p === "@nestjs/jwt", label: "your own JWTs", path: "broker" }
1252
- ];
1253
1239
  function detectAuthStacks(root) {
1254
- let pkg;
1255
1240
  try {
1256
- pkg = JSON.parse(readFileSync6(join3(root, "package.json"), "utf8"));
1241
+ return authStacksOf(dependencyNames(JSON.parse(readFileSync6(join3(root, "package.json"), "utf8"))));
1257
1242
  } catch {
1258
1243
  return [];
1259
1244
  }
1260
- const names = Object.keys({ ...pkg.devDependencies, ...pkg.dependencies });
1261
- const found = [];
1262
- for (const known of KNOWN) {
1263
- const hit = names.find(known.test);
1264
- if (hit && !found.some((f) => f.label === known.label)) found.push({ label: known.label, pkg: hit, path: known.path });
1265
- }
1266
- return found;
1267
1245
  }
1268
1246
  function appNameOf(root) {
1269
1247
  try {
@@ -1583,7 +1561,38 @@ function inspect(result, cwd) {
1583
1561
  if (result.actions && result.actions.length > 0) inspectActions(result, cwd);
1584
1562
  inspectLogin(result);
1585
1563
  }
1564
+ function supabaseSignIn(s, login) {
1565
+ 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."] };
1566
+ const passwordDirect = s.direct.includes("password");
1567
+ const why = [];
1568
+ let row;
1569
+ if (s.found.length === 0) {
1570
+ row = "your Supabase Auth, directly";
1571
+ why.push("Couldn't find where your users sign in, so AI assistants sign in to Supabase directly: a password on /mcp/login, social and email codes on Exposify's page.");
1572
+ } else if (!passwordDirect && login) {
1573
+ row = `your app's own login for passwords (${login.what}); your Supabase Auth, directly, for social and email codes`;
1574
+ why.push('Your password sign-in runs on your server, which may hold your own checks, so the form runs it. Switch to "Sign in with Supabase" on the dashboard to skip it.');
1575
+ } else if (!passwordDirect) {
1576
+ row = "your Supabase Auth, directly, for social and email codes; no password sign-in";
1577
+ why.push(`Your app signs in on the server where the form can't run it: ${(s.unusable ?? []).join("; ")}.`);
1578
+ why.push('Switch on "Sign in with Supabase" on the dashboard to have passwords go to Supabase directly (it skips what that code checks).');
1579
+ } else {
1580
+ row = "your Supabase Auth, directly";
1581
+ why.push(`Your sign-in runs in the browser (${s.found[0]}), so there's no server check to skip.`);
1582
+ }
1583
+ if (s.callbacks?.length) why.push(`Connect's sign-ins don't go through ${s.callbacks.join(", ")}; anything those check is skipped.`);
1584
+ return { row, why };
1585
+ }
1586
1586
  function inspectLogin(result) {
1587
+ if (result.supabase) {
1588
+ const { row, why } = supabaseSignIn(result.supabase, result.login);
1589
+ console.log(`
1590
+ Sign-in for AI assistants: ${row}.`);
1591
+ for (const line of why) console.log(dim(` ${line}`));
1592
+ if (result.supabase.found.length > 1) for (const f of result.supabase.found) console.log(dim(` found ${f}`));
1593
+ if (result.supabase.direct.length && !result.supabase.authHelpers) console.log(dim(" Check it on a running app: npx @exposify/next test-sign-in"));
1594
+ return;
1595
+ }
1587
1596
  if (!result.login) return;
1588
1597
  console.log(`
1589
1598
  Sign-in for AI assistants: your app's own login, ${result.login.what} (${result.login.fields.join(", ")}).`);
@@ -1770,7 +1779,7 @@ ${bold("Step 2/5")} \xB7 Print "[agentready] MCP ready \u2026" when your server
1770
1779
  console.log(`
1771
1780
  ${green("\u2713 Already set up")}: your aiReady() config already matches. Nothing to change.`);
1772
1781
  if (tty && !args.quick) {
1773
- await signInStep(args, root, cwd, "Step 4/5", result.login);
1782
+ await signInStep(args, root, cwd, "Step 4/5", result.login, result.supabase);
1774
1783
  const code2 = await describeStep(args, toolRows(result.routes, result.actions), root, cwd, "Step 5/5");
1775
1784
  if (code2 !== 0) return code2;
1776
1785
  }
@@ -1856,7 +1865,7 @@ ${bold("Step 3/5")} \xB7 Your aiReady() config${copyNote}
1856
1865
  }
1857
1866
  const finalRoutes = verified ?? toolRows(routesAsSelected(registered, selected), actionRows.map((a) => ({ ...a, exposed: a.marker ? a.exposed : pickedActions.has(a.configKey) })));
1858
1867
  if (tty && !args.quick) {
1859
- await signInStep(args, root, cwd, "Step 4/5", result.login);
1868
+ await signInStep(args, root, cwd, "Step 4/5", result.login, result.supabase);
1860
1869
  const code2 = await describeStep(args, finalRoutes, root, cwd, "Step 5/5");
1861
1870
  if (code2 !== 0) return code2;
1862
1871
  const proposed = await proposeStep(args, verifiedActions, root, cwd);
@@ -1901,7 +1910,7 @@ ${bold("Optional")} \xB7 Save the tools agents get to ${shown}?`);
1901
1910
  }
1902
1911
  if (save) writeSnapshot(routes, file, cwd);
1903
1912
  }
1904
- async function signInStep(args, root, cwd, title, login) {
1913
+ async function signInStep(args, root, cwd, title, login, supabase) {
1905
1914
  if (!args.signIn) return;
1906
1915
  const stacks = detectAuthStacks(root);
1907
1916
  const oauth = stacks.find((s) => s.path === "oauth");
@@ -1914,11 +1923,18 @@ ${bold(title)} \xB7 How your users sign in`);
1914
1923
  console.log(dim(" The README's OAuth section lists the settings to turn on in the provider's dashboard."));
1915
1924
  return;
1916
1925
  }
1917
- const own = login ? `your app's own login, ${login.what} (${login.fields.join(", ")})` : null;
1918
- if (own) console.log(` Found ${own}. AI assistants sign in with it on a form your app serves at /mcp/login, so there's nothing to set up for it.`);
1926
+ const sb = supabase && !supabase.authHelpers ? supabaseSignIn(supabase, login) : null;
1927
+ const own = sb ? sb.row : login ? `your app's own login, ${login.what} (${login.fields.join(", ")})` : null;
1928
+ if (sb) {
1929
+ console.log(` AI assistants sign in with ${sb.row}. Nothing to set up for it.`);
1930
+ for (const line of sb.why) console.log(dim(` ${line}`));
1931
+ } else if (own) console.log(` Found ${own}. AI assistants sign in with it on a form your app serves at /mcp/login, so there's nothing to set up for it.`);
1932
+ const signedOut = "agents use your app signed out, and your own checks decide what they can do";
1919
1933
  const have = existingKey(root, process.env);
1920
1934
  if (have) {
1921
- console.log(green(` \u2713 ${KEY_ENV} is set (${have.from}):`) + " agents sign in through Exposify's broker.");
1935
+ console.log(
1936
+ green(` \u2713 ${KEY_ENV} is set (${have.from}):`) + (sb ? ` agents sign in with ${sb.row}, through Exposify's broker.` : own ? " agents sign in with your app's login, through Exposify's broker." : ` until you add a sign-in method in the dashboard, ${signedOut}.`)
1937
+ );
1922
1938
  return;
1923
1939
  }
1924
1940
  const via = stacks[0] ? `With ${stacks[0].label}` : "With your own login";
@@ -1950,8 +1966,11 @@ ${bold(title)} \xB7 How your users sign in`);
1950
1966
  process.env[KEY_ENV] = outcome.key;
1951
1967
  console.log(green(` \u2713 ${written.created ? "Created" : "Updated"} ${relative(cwd, written.path) || written.path} with ${KEY_ENV}`) + ` (project "${outcome.projectName}").`);
1952
1968
  if (!written.ignored) console.log(yellow(` ! Add .env to .gitignore: it now holds a secret.`));
1953
- if (own) console.log(` Your app tells Exposify about its login when it starts with ${KEY_ENV}: nothing to add in the dashboard.`);
1954
- else console.log(` Next, in the dashboard: add how your users log in (presets for GitHub, Google, Supabase, \u2026).`);
1969
+ if (own) console.log(` Your app tells Exposify about its ${sb ? "Supabase Auth" : "login"} when it starts with ${KEY_ENV}: nothing to add in the dashboard.`);
1970
+ else {
1971
+ console.log(` No login form was found, so ${signedOut}.`);
1972
+ console.log(` To have agents act as a signed-in user, add a sign-in method in the dashboard (presets for GitHub, Google, Supabase, \u2026).`);
1973
+ }
1955
1974
  console.log(dim(` Deploying? Set ${KEY_ENV} there too. Restart your server to pick it up.`));
1956
1975
  }
1957
1976
  function serviceConfig(args) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exposify/cli",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
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.13"
25
+ "@exposify/core": "0.4.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/node": "^22.0.0",
@@ -33,7 +33,7 @@
33
33
  "publishConfig": {
34
34
  "access": "public"
35
35
  },
36
- "description": "The framework-neutral engine behind exposify-express and exposify-next: guided setup, inspect and describe for Exposify adapters.",
36
+ "description": "The framework-neutral engine behind @exposify/express and @exposify/next: guided setup, inspect and describe for Exposify adapters.",
37
37
  "author": "Exposify",
38
38
  "keywords": [
39
39
  "mcp",