@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 +1 -1
- package/dist/index.d.ts +17 -1
- package/dist/index.js +51 -32
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @exposify/cli
|
|
2
2
|
|
|
3
|
-
The framework-neutral engine behind
|
|
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
|
|
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
|
-
|
|
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
|
|
1918
|
-
|
|
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(
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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",
|