@exposify/next 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/README.md +36 -0
- package/dist/config.cjs +361 -90
- package/dist/config.d.cts +49 -3
- package/dist/config.d.ts +49 -3
- package/dist/config.js +355 -84
- package/dist/exposify-next.js +613 -125
- package/dist/index.cjs +134 -36
- package/dist/index.d.cts +27 -123
- package/dist/index.d.ts +27 -123
- package/dist/index.js +142 -36
- package/dist/manifest-DZQCvzQy.d.cts +176 -0
- package/dist/manifest-DZQCvzQy.d.ts +176 -0
- package/package.json +3 -3
- package/dist/fields-olvyFnxj.d.cts +0 -23
- package/dist/fields-olvyFnxj.d.ts +0 -23
package/dist/index.cjs
CHANGED
|
@@ -698,6 +698,7 @@ function errorMessage(err) {
|
|
|
698
698
|
}
|
|
699
699
|
async function routesFrom(manifest, cfg, warnings) {
|
|
700
700
|
const listed = Array.isArray(cfg.expose) ? cfg.expose.map(import_core.parseMethodEntry).filter((e) => e !== null) : [];
|
|
701
|
+
const callbacks = new Set(manifest.supabase?.callbacks ?? []);
|
|
701
702
|
const perEntry = await Promise.all(
|
|
702
703
|
manifest.routes.map(async (entry) => {
|
|
703
704
|
const base = { path: entry.path, params: entry.params };
|
|
@@ -715,7 +716,7 @@ async function routesFrom(manifest, cfg, warnings) {
|
|
|
715
716
|
const methods = mod ? HTTP_METHODS.filter((m) => typeof mod[m] === "function") : entry.methods;
|
|
716
717
|
return methods.filter((m) => m !== "OPTIONS").map((method) => {
|
|
717
718
|
const marker = mod ? (0, import_core.getMarker)(mod[method]) : void 0;
|
|
718
|
-
return { ...base, method, ...marker ? { marker } : {} };
|
|
719
|
+
return { ...base, method, ...marker ? { marker } : {}, ...callbacks.has(entry.file) ? { optIn: true } : {} };
|
|
719
720
|
});
|
|
720
721
|
})
|
|
721
722
|
);
|
|
@@ -873,29 +874,65 @@ function pagesFrom(manifest, cfg, config) {
|
|
|
873
874
|
}
|
|
874
875
|
return { routes, hidden: hidden3, forms };
|
|
875
876
|
}
|
|
876
|
-
function loginSpec(manifest,
|
|
877
|
+
function loginSpec(manifest, options, warnings) {
|
|
878
|
+
const config = options.login;
|
|
877
879
|
if (config === false) return null;
|
|
878
|
-
|
|
880
|
+
let login;
|
|
881
|
+
if (config === void 0) login = manifest.login ?? null;
|
|
882
|
+
else {
|
|
883
|
+
const step = stepSpec(manifest, config, (name) => (0, import_core.fieldKind)(name), warnings, "login", "AI assistants can't sign in with your app's login");
|
|
884
|
+
login = step ? { ...step.step, where: step.where } : null;
|
|
885
|
+
}
|
|
886
|
+
if (!login || options.loginVerify === void 0) return login;
|
|
887
|
+
const { verify: _found, ...first } = login;
|
|
888
|
+
if (options.loginVerify === false) return first;
|
|
889
|
+
const kind = (name) => (0, import_core.verifyFields)([name], true)?.[0]?.kind ?? (0, import_core.fieldKind)(name);
|
|
890
|
+
const verify = stepSpec(manifest, options.loginVerify, kind, warnings, "loginVerify", "AI assistants can't finish your app's two-step login");
|
|
891
|
+
return verify ? { ...first, verify: verify.step, where: `${first.where ?? "your login"}, then a code: ${verify.where}` } : first;
|
|
892
|
+
}
|
|
893
|
+
function supabaseSpec(manifest, options, warnings) {
|
|
894
|
+
if (!manifest.supabase || options.supabase === false) return null;
|
|
895
|
+
if (options.supabase) return options.supabase;
|
|
896
|
+
const e = env3();
|
|
897
|
+
const url = e.NEXT_PUBLIC_SUPABASE_URL ?? e.SUPABASE_URL;
|
|
898
|
+
const key = e.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY ?? e.NEXT_PUBLIC_SUPABASE_ANON_KEY ?? e.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY;
|
|
899
|
+
if (!url || !key) {
|
|
900
|
+
warnings.push("your app uses Supabase Auth, but NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY (or _ANON_KEY) aren't both set where the MCP route runs, so AI assistants can't sign in to it; set them, or pass aiReady({ supabase: { url, key } })");
|
|
901
|
+
return null;
|
|
902
|
+
}
|
|
903
|
+
try {
|
|
904
|
+
new URL(url);
|
|
905
|
+
} catch {
|
|
906
|
+
warnings.push(`NEXT_PUBLIC_SUPABASE_URL isn't a URL (${url}), so AI assistants can't sign in to your Supabase Auth`);
|
|
907
|
+
return null;
|
|
908
|
+
}
|
|
909
|
+
return { url, key };
|
|
910
|
+
}
|
|
911
|
+
function stepSpec(manifest, config, kind, warnings, option, consequence) {
|
|
879
912
|
if (typeof config === "object") {
|
|
880
|
-
const fields2 = config.fields.map((name) => ({ name, kind: (
|
|
881
|
-
return { kind: "route", path: config.path, fields: fields2, body: config.body ?? "json", where: `POST ${config.path}` };
|
|
913
|
+
const fields2 = config.fields.map((name) => ({ name, kind: kind(name) }));
|
|
914
|
+
return { step: { kind: "route", path: config.path, fields: fields2, body: config.body ?? "json" }, where: `POST ${config.path}` };
|
|
882
915
|
}
|
|
883
916
|
const action = (manifest.actions ?? []).find((a) => a.key === config || a.export === config);
|
|
884
917
|
if (!action) {
|
|
885
|
-
warnings.push(`aiReady({
|
|
918
|
+
warnings.push(`aiReady({ ${option}: "${config}" }): no server action by that name, so ${consequence}`);
|
|
886
919
|
return null;
|
|
887
920
|
}
|
|
888
|
-
const fields = action.fields.map((f) => ({ name: f.name, kind: (
|
|
889
|
-
return { kind: "action", key: action.key, fields, ...action.host ? { host: action.host } : {}, where: `${action.export} in ${action.file}` };
|
|
921
|
+
const fields = action.fields.map((f) => ({ name: f.name, kind: kind(f.name) }));
|
|
922
|
+
return { step: { kind: "action", key: action.key, fields, ...action.host ? { host: action.host } : {} }, where: `${action.export} in ${action.file}` };
|
|
890
923
|
}
|
|
891
|
-
async function actionLoginResult(raw, exportName) {
|
|
924
|
+
async function actionLoginResult(raw, exportName, reading = {}) {
|
|
892
925
|
if (actionNotFound(raw)) return { ok: false, message: "The app's sign-in page didn't run its login. Its developer needs to rebuild the app." };
|
|
893
|
-
const
|
|
926
|
+
const changes = (0, import_core.cookieChanges)(raw);
|
|
894
927
|
const out = await readActionResponse(raw, exportName);
|
|
895
928
|
const body = await out.json().catch(() => void 0);
|
|
929
|
+
const location = raw.headers.get("x-action-redirect")?.split(";")[0] ?? (typeof body?.redirectedTo === "string" ? body.redirectedTo : null);
|
|
930
|
+
const step = (0, import_core.readStep)({ status: location ? 303 : out.status, location, body, cookies: changes }, reading);
|
|
931
|
+
if (step) return (0, import_core.keepFailedCookies)(step, changes, reading);
|
|
896
932
|
const message = (0, import_core.errorMessageOf)(body);
|
|
897
|
-
if (out.status >= 400 || message && !body?.redirectedTo) return { ok: false, message };
|
|
898
|
-
|
|
933
|
+
if (out.status >= 400 || message && !body?.redirectedTo || (0, import_core.rejectedBody)(body)) return (0, import_core.keepFailedCookies)({ ok: false, message }, changes, reading);
|
|
934
|
+
const session = (0, import_core.sessionFrom)(changes, reading, Boolean(location) || body !== void 0 && body !== null && typeof body === "object");
|
|
935
|
+
return session ? { ok: true, session } : (0, import_core.keepFailedCookies)({ ok: false, message }, changes, reading);
|
|
899
936
|
}
|
|
900
937
|
var clientIp = (headers) => headers.get("x-forwarded-for")?.split(",")[0]?.trim() || headers.get("x-real-ip") || void 0;
|
|
901
938
|
function aiReady(manifest, options = {}) {
|
|
@@ -908,9 +945,39 @@ function aiReady(manifest, options = {}) {
|
|
|
908
945
|
const actionsBase = (manifest.mcpPath ?? "").replace(/\/$/, "");
|
|
909
946
|
const actionUrl = manifest.mcpPath === void 0 ? null : new RegExp(`${actionsBase.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/actions/([^/]+)/?$`);
|
|
910
947
|
const loginWarnings = [];
|
|
911
|
-
const login = (0, import_core.hostedResolverInfo)(cfg.auth) ? loginSpec(manifest, options
|
|
948
|
+
const login = (0, import_core.hostedResolverInfo)(cfg.auth) ? loginSpec(manifest, options, loginWarnings) : null;
|
|
912
949
|
const loginPagePath = `${(manifest.mcpPath ?? "/mcp").replace(/\/$/, "")}/login`;
|
|
913
950
|
if (login) cfg.signedOutPaths = [login.kind === "action" ? login.host : login.path].filter((p) => Boolean(p)).map((p) => `${manifest.basePath ?? ""}${hostPath(p)}`);
|
|
951
|
+
const supabase = (0, import_core.hostedResolverInfo)(cfg.auth) ? supabaseSpec(manifest, options, loginWarnings) : null;
|
|
952
|
+
if (supabase && manifest.supabase?.loginPages?.length) {
|
|
953
|
+
cfg.signedOutPaths = [...cfg.signedOutPaths ?? [], ...manifest.supabase.loginPages.map((p) => `${manifest.basePath ?? ""}${hostPath(p)}`)];
|
|
954
|
+
}
|
|
955
|
+
const supabasePassword = supabase ? (0, import_core.supabasePasswordLogin)({
|
|
956
|
+
...supabase,
|
|
957
|
+
onCaptcha: () => warnOnce("your Supabase project asks for a CAPTCHA to sign in, so AI assistants can't sign in with a password; social sign-ins still work")
|
|
958
|
+
}) : null;
|
|
959
|
+
const supabaseLoginNow = () => {
|
|
960
|
+
if (!supabasePassword) return null;
|
|
961
|
+
const choice = (0, import_core.hostedResolverInfo)(cfg.auth)?.supabasePasswordDirect();
|
|
962
|
+
return choice ?? (!login && manifest.supabase?.direct.includes("password")) ? supabasePassword : null;
|
|
963
|
+
};
|
|
964
|
+
let supabaseAuth;
|
|
965
|
+
const supabaseReport = () => {
|
|
966
|
+
if (!supabase || !manifest.supabase) return Promise.resolve(void 0);
|
|
967
|
+
const found = manifest.supabase;
|
|
968
|
+
supabaseAuth ??= (0, import_core.readSupabaseAuth)(supabase.url, supabase.key).then((read) => {
|
|
969
|
+
if (!read) {
|
|
970
|
+
supabaseAuth = void 0;
|
|
971
|
+
return void 0;
|
|
972
|
+
}
|
|
973
|
+
return {
|
|
974
|
+
...read,
|
|
975
|
+
cookie: found.cookieName ?? (0, import_core.supabaseCookieName)(supabase.url),
|
|
976
|
+
found: { direct: found.direct, where: found.found, ...found.unusable ? { unusable: found.unusable } : {}, ...found.callbacks ? { callbacks: found.callbacks } : {} }
|
|
977
|
+
};
|
|
978
|
+
});
|
|
979
|
+
return supabaseAuth;
|
|
980
|
+
};
|
|
914
981
|
let built = null;
|
|
915
982
|
const build = async (endpoint) => {
|
|
916
983
|
const warnings = [...loginWarnings];
|
|
@@ -1014,7 +1081,12 @@ function aiReady(manifest, options = {}) {
|
|
|
1014
1081
|
if (cfg.expose === "*") details.push('all write routes exposed (expose: "*")');
|
|
1015
1082
|
if (counts.hiddenWrites > 0) details.push(`${counts.hiddenWrites} write route${counts.hiddenWrites === 1 ? "" : "s"} hidden (opt in with exposed())`);
|
|
1016
1083
|
if (pages.routes.length > 0) details.push(`${pages.routes.length} page${pages.routes.length === 1 ? "" : "s"} readable`);
|
|
1017
|
-
if (
|
|
1084
|
+
if (supabaseLoginNow()) {
|
|
1085
|
+
details.push(`AI assistants sign in to your Supabase Auth with a password at ${loginPagePath}`);
|
|
1086
|
+
} else if (login) {
|
|
1087
|
+
const step = (l) => l.kind === "action" ? `server action ${l.key}` : `POST ${l.path}`;
|
|
1088
|
+
details.push(`AI assistants sign in with your app's own login (${step(login)}${login.verify ? `, then a code: ${step(login.verify)}` : ""}) at ${loginPagePath}`);
|
|
1089
|
+
}
|
|
1018
1090
|
if (pages.forms > 0) details.push(`${pages.forms} page${pages.forms === 1 ? "" : "s"} left out as just a form (list in aiReady({ pages }) to include)`);
|
|
1019
1091
|
if (exposedActions > 0) details.push(`${exposedActions} server action${exposedActions === 1 ? "" : "s"} exposed, each checking auth itself`);
|
|
1020
1092
|
if (hiddenActions > 0) details.push(`${hiddenActions} server action${hiddenActions === 1 ? "" : "s"} hidden (opt in with aiReady({ actions }))`);
|
|
@@ -1086,34 +1158,34 @@ function aiReady(manifest, options = {}) {
|
|
|
1086
1158
|
const reply = encodeReply(actionArguments(live.action.params, fields, jsonFieldsOf(live.action)));
|
|
1087
1159
|
return fetch(new URL(path, origin), { method: "POST", headers: out, body: reply, redirect: "manual", cache: "no-store" });
|
|
1088
1160
|
};
|
|
1089
|
-
const appLoginFor = (req, spec) =>
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1161
|
+
const appLoginFor = (req, spec) => {
|
|
1162
|
+
const pathOf = (p) => {
|
|
1163
|
+
let path = `${manifest.basePath ?? ""}${p}`;
|
|
1164
|
+
if (manifest.trailingSlash && !path.endsWith("/")) path += "/";
|
|
1165
|
+
return path;
|
|
1166
|
+
};
|
|
1167
|
+
const stepPath = (step) => step.kind === "action" ? step.host ? pathOf(hostPath(step.host)) : void 0 : pathOf(step.path);
|
|
1168
|
+
const verifyPaths = spec.verify ? [stepPath(spec.verify)].filter((p) => Boolean(p)) : void 0;
|
|
1169
|
+
const send = async (step, values, ctx, reading, cookies = []) => {
|
|
1094
1170
|
const target = options.baseUrl ?? env3().EXPOSIFY_BASE_URL ?? new URL(req.url).origin;
|
|
1095
1171
|
const pub = new URL(ctx.origin);
|
|
1096
1172
|
const headers = new Headers({ origin: pub.origin, "x-forwarded-proto": pub.protocol.slice(0, -1), "x-forwarded-host": pub.host });
|
|
1097
1173
|
if (ctx.ip) headers.set("x-forwarded-for", ctx.ip);
|
|
1098
1174
|
const ua = req.headers.get("user-agent");
|
|
1099
1175
|
if (ua) headers.set("user-agent", ua);
|
|
1176
|
+
if (cookies.length) headers.set("cookie", cookies.map((c) => `${c.name}=${c.value}`).join("; "));
|
|
1100
1177
|
const bypass = env3().VERCEL_AUTOMATION_BYPASS_SECRET;
|
|
1101
1178
|
if (bypass) headers.set("x-vercel-protection-bypass", bypass);
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
return path;
|
|
1106
|
-
};
|
|
1107
|
-
if (spec.kind === "route") {
|
|
1108
|
-
headers.set("content-type", spec.body === "form" ? "application/x-www-form-urlencoded" : "application/json");
|
|
1109
|
-
const body = spec.body === "form" ? new URLSearchParams(values).toString() : JSON.stringify(values);
|
|
1110
|
-
return (0, import_core.loginResultFromResponse)(await fetch(new URL(pathOf(spec.path), target), { method: "POST", headers, body, redirect: "manual", cache: "no-store" }));
|
|
1179
|
+
if (step.kind === "route") {
|
|
1180
|
+
headers.set("content-type", step.body === "form" ? "application/x-www-form-urlencoded" : "application/json");
|
|
1181
|
+
const body = step.body === "form" ? new URLSearchParams(values).toString() : JSON.stringify(values);
|
|
1182
|
+
return (0, import_core.loginResultFromResponse)(await fetch(new URL(pathOf(step.path), target), { method: "POST", headers, body, redirect: "manual", cache: "no-store" }), reading);
|
|
1111
1183
|
}
|
|
1112
|
-
const action = (manifest.actions ?? []).find((a) => a.key ===
|
|
1184
|
+
const action = (manifest.actions ?? []).find((a) => a.key === step.key);
|
|
1113
1185
|
const fn = action ? (await action.load())[action.export] : void 0;
|
|
1114
1186
|
const id = fn?.$$id;
|
|
1115
1187
|
if (!action?.host || typeof id !== "string") {
|
|
1116
|
-
warnOnce(`server action ${
|
|
1188
|
+
warnOnce(`server action ${step.key} is part of your app's login, but no page uses it, so the sign-in form can't run it; use it from a page's form`);
|
|
1117
1189
|
return { ok: false, message: "This app's sign-in can't be used by AI assistants yet." };
|
|
1118
1190
|
}
|
|
1119
1191
|
headers.set("next-action", id);
|
|
@@ -1121,9 +1193,28 @@ function aiReady(manifest, options = {}) {
|
|
|
1121
1193
|
headers.set("accept", "text/x-component");
|
|
1122
1194
|
const reply = encodeReply(actionArguments(action.params, values, jsonFieldsOf(action)));
|
|
1123
1195
|
const raw = await fetch(new URL(pathOf(hostPath(action.host)), target), { method: "POST", headers, body: reply, redirect: "manual", cache: "no-store" });
|
|
1124
|
-
return actionLoginResult(raw, action.export);
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1196
|
+
return actionLoginResult(raw, action.export, reading);
|
|
1197
|
+
};
|
|
1198
|
+
const verify = spec.verify;
|
|
1199
|
+
return {
|
|
1200
|
+
label: spec.kind === "action" ? `server action ${spec.key}` : `POST ${spec.path}`,
|
|
1201
|
+
fields: spec.fields,
|
|
1202
|
+
signedOutPaths: cfg.signedOutPaths ?? [],
|
|
1203
|
+
...spec.passwordless ? { passwordless: true } : {},
|
|
1204
|
+
attempt: (values, ctx) => send(spec, values, ctx, {
|
|
1205
|
+
...verifyPaths ? { verifyPaths } : {},
|
|
1206
|
+
...verify ? { verifyFields: verify.fields.map((f) => f.name) } : {},
|
|
1207
|
+
...spec.passwordless ? { start: true } : {}
|
|
1208
|
+
}),
|
|
1209
|
+
...verify ? {
|
|
1210
|
+
verify: {
|
|
1211
|
+
label: verify.kind === "action" ? `server action ${verify.key}` : `POST ${verify.path}`,
|
|
1212
|
+
fields: verify.fields,
|
|
1213
|
+
attempt: (values, ctx) => send(verify, values, ctx, { carried: ctx.carry.cookies, verifyPaths: verifyPaths ?? [] }, ctx.carry.cookies)
|
|
1214
|
+
}
|
|
1215
|
+
} : {}
|
|
1216
|
+
};
|
|
1217
|
+
};
|
|
1127
1218
|
const carrySession = (raw, out) => {
|
|
1128
1219
|
const lines = (0, import_core.setCookieLines)(raw);
|
|
1129
1220
|
const redirect = raw.headers.get("x-action-redirect")?.split(";")[0];
|
|
@@ -1168,9 +1259,10 @@ function aiReady(manifest, options = {}) {
|
|
|
1168
1259
|
};
|
|
1169
1260
|
return async (req) => {
|
|
1170
1261
|
const pathname = new URL(req.url).pathname;
|
|
1171
|
-
|
|
1262
|
+
const supabaseLogin = supabaseLoginNow();
|
|
1263
|
+
if ((login || supabaseLogin) && (0, import_core.samePath)(pathname, [loginPagePath, `${manifest.basePath ?? ""}${loginPagePath}`])) {
|
|
1172
1264
|
const pub = new URL((0, import_core.publicRequestUrl)(req.url, req.headers));
|
|
1173
|
-
return (0, import_core.handleLoginPage)(req, appLoginFor(req, login), (0, import_core.hostedResolverInfo)(cfg.auth), {
|
|
1265
|
+
return (0, import_core.handleLoginPage)(req, supabaseLogin ?? appLoginFor(req, login), (0, import_core.hostedResolverInfo)(cfg.auth), {
|
|
1174
1266
|
path: pub.pathname,
|
|
1175
1267
|
secure: pub.protocol === "https:",
|
|
1176
1268
|
origin: pub.origin,
|
|
@@ -1197,7 +1289,13 @@ function aiReady(manifest, options = {}) {
|
|
|
1197
1289
|
const url = (0, import_core.publicRequestUrl)(req.url, req.headers);
|
|
1198
1290
|
const isPrm = url.endsWith(PRM_SUFFIX);
|
|
1199
1291
|
const resource = brokerGate.resourceFor(isPrm ? url.slice(0, -PRM_SUFFIX.length) : url);
|
|
1200
|
-
const
|
|
1292
|
+
const supabaseAuth2 = await supabaseReport();
|
|
1293
|
+
const verdict = await (0, import_core.reportAppLogin)(cfg.auth, resource, supabaseLogin ?? login, {
|
|
1294
|
+
where: supabaseLogin ? "your Supabase Auth (password)" : login?.where,
|
|
1295
|
+
stacks: manifest.authStacks,
|
|
1296
|
+
log: logReady,
|
|
1297
|
+
...supabaseAuth2 ? { supabase: supabaseAuth2 } : {}
|
|
1298
|
+
});
|
|
1201
1299
|
if (verdict === "open") {
|
|
1202
1300
|
if (isPrm) return new Response("not found", { status: 404 });
|
|
1203
1301
|
} else {
|
package/dist/index.d.cts
CHANGED
|
@@ -1,126 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RouteMarker, AgentReadyOptions } from '@exposify/core';
|
|
2
2
|
export { HostedAuthResolverConfig, completeMcpLogin, createHostedAuthResolver, describe, exposed, hidden, hostedBroker } from '@exposify/core';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
interface AppRouteEntry {
|
|
6
|
-
router: "app";
|
|
7
|
-
/** `/api/orders/:id`; catch-alls as `:slug*`, optional ones as `:slug*?`. */
|
|
8
|
-
path: string;
|
|
9
|
-
params: string[];
|
|
10
|
-
/** Project-relative, for messages. */
|
|
11
|
-
file: string;
|
|
12
|
-
/** Exported methods when generated; the loaded module's exports win at runtime. */
|
|
13
|
-
methods: string[];
|
|
14
|
-
/** Loads the route module to read exposed()/hidden()/describe() off its exports. Absent for edge routes. */
|
|
15
|
-
load?: () => Promise<Record<string, unknown>>;
|
|
16
|
-
/** `export const runtime = "edge"`: not loaded into the Node MCP route, so markers there aren't read. */
|
|
17
|
-
edge?: boolean;
|
|
18
|
-
}
|
|
19
|
-
interface PagesRouteEntry {
|
|
20
|
-
router: "pages";
|
|
21
|
-
path: string;
|
|
22
|
-
params: string[];
|
|
23
|
-
file: string;
|
|
24
|
-
}
|
|
25
|
-
type ManifestRoute = AppRouteEntry | PagesRouteEntry;
|
|
26
|
-
/** An exported function of a `"use server"` module. */
|
|
27
|
-
interface ManifestAction {
|
|
28
|
-
/** Unique among actions: the default tool name, and the URL segment under `<mcpPath>/actions/`. */
|
|
29
|
-
key: string;
|
|
30
|
-
file: string;
|
|
31
|
-
export: string;
|
|
32
|
-
/** How each parameter is filled on a call, in order: see `ActionParamRole` in parse.ts. */
|
|
33
|
-
params: Array<{
|
|
34
|
-
name: string;
|
|
35
|
-
role: "formData" | "prev" | "input" | "object";
|
|
36
|
-
}>;
|
|
37
|
-
/** It sets or deletes a cookie, which would land on exposify's internal call rather than the agent's client. */
|
|
38
|
-
setsCookies?: boolean;
|
|
39
|
-
/** What it takes, worked out from its code (fields.ts): its tool schema when the app doesn't write one. */
|
|
40
|
-
fields: ActionField[];
|
|
41
|
-
/** It reads every form field, so its code doesn't say which exist. */
|
|
42
|
-
readsAll?: boolean;
|
|
43
|
-
/** What the analysis couldn't work out ("line N: …"): with any, the app writes the schema. */
|
|
44
|
-
unresolved?: string[];
|
|
45
|
-
/** The forms that submit to it (pass 3), where they are. */
|
|
46
|
-
forms?: Array<{
|
|
47
|
-
file: string;
|
|
48
|
-
line: number;
|
|
49
|
-
}>;
|
|
50
|
-
/** An input `describe` proposed and a person accepted, current with the code: used when `fields` can't be the schema. */
|
|
51
|
-
proposed?: ProposedField[];
|
|
52
|
-
/**
|
|
53
|
-
* A page that uses it (`/checkout`, `/admin/orders/:id`): tool calls are
|
|
54
|
-
* sent there as a real Server Action request. Absent when no page uses it;
|
|
55
|
-
* it's then called in-process by the MCP route.
|
|
56
|
-
*/
|
|
57
|
-
host?: string;
|
|
58
|
-
/**
|
|
59
|
-
* The app's own checks it runs first (`lib/auth.ts#requireAdmin`): a
|
|
60
|
-
* user who doesn't pass one doesn't see the tool (guards.ts).
|
|
61
|
-
*/
|
|
62
|
-
guards?: string[];
|
|
63
|
-
load: () => Promise<Record<string, unknown>>;
|
|
64
|
-
}
|
|
65
|
-
/** An App Router page: a read tool that fetches it like a browser (pages.ts). */
|
|
66
|
-
interface ManifestPage {
|
|
67
|
-
/** Tool name, e.g. `view_product_slug`. */
|
|
68
|
-
name: string;
|
|
69
|
-
/** `/product/:slug`, as routes are written. */
|
|
70
|
-
path: string;
|
|
71
|
-
params: string[];
|
|
72
|
-
file: string;
|
|
73
|
-
/** Search params its code reads: optional tool arguments, sent as the query. */
|
|
74
|
-
search?: string[];
|
|
75
|
-
/** From its static `metadata`. */
|
|
76
|
-
title?: string;
|
|
77
|
-
description?: string;
|
|
78
|
-
/** Just a form posting to a Server Action: not a tool unless `pages` lists it. */
|
|
79
|
-
formOnly?: boolean;
|
|
80
|
-
/** The app's own checks it runs (`lib/auth.ts#requireAdmin`), see ManifestAction.guards. */
|
|
81
|
-
guards?: string[];
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* The app's own login (login.ts): a sign-in form under the MCP route makes
|
|
85
|
-
* this request, for Connect with no code. `host` is the page with the
|
|
86
|
-
* action's form, which tool calls redirected to mean "signed out".
|
|
87
|
-
*/
|
|
88
|
-
type ManifestLogin = ({
|
|
89
|
-
kind: "action";
|
|
90
|
-
key: string;
|
|
91
|
-
fields: LoginField[];
|
|
92
|
-
host?: string;
|
|
93
|
-
} | {
|
|
94
|
-
kind: "route";
|
|
95
|
-
path: string;
|
|
96
|
-
fields: LoginField[];
|
|
97
|
-
body: "json" | "form";
|
|
98
|
-
}) & {
|
|
99
|
-
/** Where it is, for the startup log and the dashboard: "signIn in app/actions/auth.ts". */
|
|
100
|
-
where?: string;
|
|
101
|
-
};
|
|
102
|
-
interface ExposifyManifest {
|
|
103
|
-
info: {
|
|
104
|
-
name: string;
|
|
105
|
-
version: string;
|
|
106
|
-
};
|
|
107
|
-
/** next.config's basePath, prefixed to every tool call's path. */
|
|
108
|
-
basePath?: string;
|
|
109
|
-
/** next.config's trailingSlash: tool calls add one, so they aren't redirected. */
|
|
110
|
-
trailingSlash?: boolean;
|
|
111
|
-
routes: ManifestRoute[];
|
|
112
|
-
/** The MCP route's URL without its catch-all segment, e.g. `/mcp`: Server Actions are served under `<mcpPath>/actions/`. */
|
|
113
|
-
mcpPath?: string;
|
|
114
|
-
actions?: ManifestAction[];
|
|
115
|
-
pages?: ManifestPage[];
|
|
116
|
-
login?: ManifestLogin;
|
|
117
|
-
/** Auth libraries in package.json ("next-auth", "@supabase/ssr", ...), reported with the login. */
|
|
118
|
-
authStacks?: string[];
|
|
119
|
-
/** Docs for search_docs, embedded so they work without a file system. */
|
|
120
|
-
docs: DocSource[];
|
|
121
|
-
/** exposify.descriptions.json's texts, "METHOD /path" -> text. */
|
|
122
|
-
descriptions?: Record<string, string>;
|
|
123
|
-
}
|
|
3
|
+
import { E as ExposifyManifest } from './manifest-DZQCvzQy.cjs';
|
|
4
|
+
export { a as AppRouteEntry, b as ManifestAction, c as ManifestLogin, d as ManifestLoginStep, e as ManifestPage, f as ManifestRoute, P as PagesRouteEntry } from './manifest-DZQCvzQy.cjs';
|
|
124
5
|
|
|
125
6
|
/** Header carrying `<ip>;<unix seconds>;<signature>` on a tool call. */
|
|
126
7
|
declare const AGENT_IP_HEADER = "x-exposify-agent-ip";
|
|
@@ -205,6 +86,29 @@ interface NextAiReadyOptions extends Omit<AgentReadyOptions, "docs"> {
|
|
|
205
86
|
fields: string[];
|
|
206
87
|
body?: "json" | "form";
|
|
207
88
|
};
|
|
89
|
+
/**
|
|
90
|
+
* The login's second step, when it asks for a code after the first (a
|
|
91
|
+
* password, then a code; or an email, then a code). Found by
|
|
92
|
+
* `withExposify` (a Server Action or POST route named like `verify`,
|
|
93
|
+
* `2fa`, `otp` that takes one code and sets the session). Name another
|
|
94
|
+
* Server Action, a route (`{ path, fields }`), or `false` for none.
|
|
95
|
+
*/
|
|
96
|
+
loginVerify?: false | string | {
|
|
97
|
+
path: string;
|
|
98
|
+
fields: string[];
|
|
99
|
+
body?: "json" | "form";
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* The app's Supabase project, for Connect when the app uses Supabase Auth
|
|
103
|
+
* with `@supabase/ssr`: AI assistants sign in to it directly, and tool
|
|
104
|
+
* calls carry the session cookie `@supabase/ssr` reads. Default: read from
|
|
105
|
+
* `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY`
|
|
106
|
+
* (or `NEXT_PUBLIC_SUPABASE_ANON_KEY`). `false` to not offer it.
|
|
107
|
+
*/
|
|
108
|
+
supabase?: false | {
|
|
109
|
+
url: string;
|
|
110
|
+
key: string;
|
|
111
|
+
};
|
|
208
112
|
}
|
|
209
113
|
interface NextPageOptions {
|
|
210
114
|
/** Tool name. Default: `view_` and the path, snake_cased. */
|
|
@@ -214,4 +118,4 @@ interface NextPageOptions {
|
|
|
214
118
|
type NextMcpHandler = (req: Request) => Promise<Response>;
|
|
215
119
|
declare function aiReady(manifest: ExposifyManifest, options?: NextAiReadyOptions): NextMcpHandler;
|
|
216
120
|
|
|
217
|
-
export { AGENT_IP_HEADER,
|
|
121
|
+
export { AGENT_IP_HEADER, DISPATCH_HEADER, ExposifyManifest, type NextActionOptions, type NextAiReadyOptions, type NextMcpHandler, type NextPageOptions, agentIp, aiReady };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,126 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RouteMarker, AgentReadyOptions } from '@exposify/core';
|
|
2
2
|
export { HostedAuthResolverConfig, completeMcpLogin, createHostedAuthResolver, describe, exposed, hidden, hostedBroker } from '@exposify/core';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
interface AppRouteEntry {
|
|
6
|
-
router: "app";
|
|
7
|
-
/** `/api/orders/:id`; catch-alls as `:slug*`, optional ones as `:slug*?`. */
|
|
8
|
-
path: string;
|
|
9
|
-
params: string[];
|
|
10
|
-
/** Project-relative, for messages. */
|
|
11
|
-
file: string;
|
|
12
|
-
/** Exported methods when generated; the loaded module's exports win at runtime. */
|
|
13
|
-
methods: string[];
|
|
14
|
-
/** Loads the route module to read exposed()/hidden()/describe() off its exports. Absent for edge routes. */
|
|
15
|
-
load?: () => Promise<Record<string, unknown>>;
|
|
16
|
-
/** `export const runtime = "edge"`: not loaded into the Node MCP route, so markers there aren't read. */
|
|
17
|
-
edge?: boolean;
|
|
18
|
-
}
|
|
19
|
-
interface PagesRouteEntry {
|
|
20
|
-
router: "pages";
|
|
21
|
-
path: string;
|
|
22
|
-
params: string[];
|
|
23
|
-
file: string;
|
|
24
|
-
}
|
|
25
|
-
type ManifestRoute = AppRouteEntry | PagesRouteEntry;
|
|
26
|
-
/** An exported function of a `"use server"` module. */
|
|
27
|
-
interface ManifestAction {
|
|
28
|
-
/** Unique among actions: the default tool name, and the URL segment under `<mcpPath>/actions/`. */
|
|
29
|
-
key: string;
|
|
30
|
-
file: string;
|
|
31
|
-
export: string;
|
|
32
|
-
/** How each parameter is filled on a call, in order: see `ActionParamRole` in parse.ts. */
|
|
33
|
-
params: Array<{
|
|
34
|
-
name: string;
|
|
35
|
-
role: "formData" | "prev" | "input" | "object";
|
|
36
|
-
}>;
|
|
37
|
-
/** It sets or deletes a cookie, which would land on exposify's internal call rather than the agent's client. */
|
|
38
|
-
setsCookies?: boolean;
|
|
39
|
-
/** What it takes, worked out from its code (fields.ts): its tool schema when the app doesn't write one. */
|
|
40
|
-
fields: ActionField[];
|
|
41
|
-
/** It reads every form field, so its code doesn't say which exist. */
|
|
42
|
-
readsAll?: boolean;
|
|
43
|
-
/** What the analysis couldn't work out ("line N: …"): with any, the app writes the schema. */
|
|
44
|
-
unresolved?: string[];
|
|
45
|
-
/** The forms that submit to it (pass 3), where they are. */
|
|
46
|
-
forms?: Array<{
|
|
47
|
-
file: string;
|
|
48
|
-
line: number;
|
|
49
|
-
}>;
|
|
50
|
-
/** An input `describe` proposed and a person accepted, current with the code: used when `fields` can't be the schema. */
|
|
51
|
-
proposed?: ProposedField[];
|
|
52
|
-
/**
|
|
53
|
-
* A page that uses it (`/checkout`, `/admin/orders/:id`): tool calls are
|
|
54
|
-
* sent there as a real Server Action request. Absent when no page uses it;
|
|
55
|
-
* it's then called in-process by the MCP route.
|
|
56
|
-
*/
|
|
57
|
-
host?: string;
|
|
58
|
-
/**
|
|
59
|
-
* The app's own checks it runs first (`lib/auth.ts#requireAdmin`): a
|
|
60
|
-
* user who doesn't pass one doesn't see the tool (guards.ts).
|
|
61
|
-
*/
|
|
62
|
-
guards?: string[];
|
|
63
|
-
load: () => Promise<Record<string, unknown>>;
|
|
64
|
-
}
|
|
65
|
-
/** An App Router page: a read tool that fetches it like a browser (pages.ts). */
|
|
66
|
-
interface ManifestPage {
|
|
67
|
-
/** Tool name, e.g. `view_product_slug`. */
|
|
68
|
-
name: string;
|
|
69
|
-
/** `/product/:slug`, as routes are written. */
|
|
70
|
-
path: string;
|
|
71
|
-
params: string[];
|
|
72
|
-
file: string;
|
|
73
|
-
/** Search params its code reads: optional tool arguments, sent as the query. */
|
|
74
|
-
search?: string[];
|
|
75
|
-
/** From its static `metadata`. */
|
|
76
|
-
title?: string;
|
|
77
|
-
description?: string;
|
|
78
|
-
/** Just a form posting to a Server Action: not a tool unless `pages` lists it. */
|
|
79
|
-
formOnly?: boolean;
|
|
80
|
-
/** The app's own checks it runs (`lib/auth.ts#requireAdmin`), see ManifestAction.guards. */
|
|
81
|
-
guards?: string[];
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* The app's own login (login.ts): a sign-in form under the MCP route makes
|
|
85
|
-
* this request, for Connect with no code. `host` is the page with the
|
|
86
|
-
* action's form, which tool calls redirected to mean "signed out".
|
|
87
|
-
*/
|
|
88
|
-
type ManifestLogin = ({
|
|
89
|
-
kind: "action";
|
|
90
|
-
key: string;
|
|
91
|
-
fields: LoginField[];
|
|
92
|
-
host?: string;
|
|
93
|
-
} | {
|
|
94
|
-
kind: "route";
|
|
95
|
-
path: string;
|
|
96
|
-
fields: LoginField[];
|
|
97
|
-
body: "json" | "form";
|
|
98
|
-
}) & {
|
|
99
|
-
/** Where it is, for the startup log and the dashboard: "signIn in app/actions/auth.ts". */
|
|
100
|
-
where?: string;
|
|
101
|
-
};
|
|
102
|
-
interface ExposifyManifest {
|
|
103
|
-
info: {
|
|
104
|
-
name: string;
|
|
105
|
-
version: string;
|
|
106
|
-
};
|
|
107
|
-
/** next.config's basePath, prefixed to every tool call's path. */
|
|
108
|
-
basePath?: string;
|
|
109
|
-
/** next.config's trailingSlash: tool calls add one, so they aren't redirected. */
|
|
110
|
-
trailingSlash?: boolean;
|
|
111
|
-
routes: ManifestRoute[];
|
|
112
|
-
/** The MCP route's URL without its catch-all segment, e.g. `/mcp`: Server Actions are served under `<mcpPath>/actions/`. */
|
|
113
|
-
mcpPath?: string;
|
|
114
|
-
actions?: ManifestAction[];
|
|
115
|
-
pages?: ManifestPage[];
|
|
116
|
-
login?: ManifestLogin;
|
|
117
|
-
/** Auth libraries in package.json ("next-auth", "@supabase/ssr", ...), reported with the login. */
|
|
118
|
-
authStacks?: string[];
|
|
119
|
-
/** Docs for search_docs, embedded so they work without a file system. */
|
|
120
|
-
docs: DocSource[];
|
|
121
|
-
/** exposify.descriptions.json's texts, "METHOD /path" -> text. */
|
|
122
|
-
descriptions?: Record<string, string>;
|
|
123
|
-
}
|
|
3
|
+
import { E as ExposifyManifest } from './manifest-DZQCvzQy.js';
|
|
4
|
+
export { a as AppRouteEntry, b as ManifestAction, c as ManifestLogin, d as ManifestLoginStep, e as ManifestPage, f as ManifestRoute, P as PagesRouteEntry } from './manifest-DZQCvzQy.js';
|
|
124
5
|
|
|
125
6
|
/** Header carrying `<ip>;<unix seconds>;<signature>` on a tool call. */
|
|
126
7
|
declare const AGENT_IP_HEADER = "x-exposify-agent-ip";
|
|
@@ -205,6 +86,29 @@ interface NextAiReadyOptions extends Omit<AgentReadyOptions, "docs"> {
|
|
|
205
86
|
fields: string[];
|
|
206
87
|
body?: "json" | "form";
|
|
207
88
|
};
|
|
89
|
+
/**
|
|
90
|
+
* The login's second step, when it asks for a code after the first (a
|
|
91
|
+
* password, then a code; or an email, then a code). Found by
|
|
92
|
+
* `withExposify` (a Server Action or POST route named like `verify`,
|
|
93
|
+
* `2fa`, `otp` that takes one code and sets the session). Name another
|
|
94
|
+
* Server Action, a route (`{ path, fields }`), or `false` for none.
|
|
95
|
+
*/
|
|
96
|
+
loginVerify?: false | string | {
|
|
97
|
+
path: string;
|
|
98
|
+
fields: string[];
|
|
99
|
+
body?: "json" | "form";
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* The app's Supabase project, for Connect when the app uses Supabase Auth
|
|
103
|
+
* with `@supabase/ssr`: AI assistants sign in to it directly, and tool
|
|
104
|
+
* calls carry the session cookie `@supabase/ssr` reads. Default: read from
|
|
105
|
+
* `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY`
|
|
106
|
+
* (or `NEXT_PUBLIC_SUPABASE_ANON_KEY`). `false` to not offer it.
|
|
107
|
+
*/
|
|
108
|
+
supabase?: false | {
|
|
109
|
+
url: string;
|
|
110
|
+
key: string;
|
|
111
|
+
};
|
|
208
112
|
}
|
|
209
113
|
interface NextPageOptions {
|
|
210
114
|
/** Tool name. Default: `view_` and the path, snake_cased. */
|
|
@@ -214,4 +118,4 @@ interface NextPageOptions {
|
|
|
214
118
|
type NextMcpHandler = (req: Request) => Promise<Response>;
|
|
215
119
|
declare function aiReady(manifest: ExposifyManifest, options?: NextAiReadyOptions): NextMcpHandler;
|
|
216
120
|
|
|
217
|
-
export { AGENT_IP_HEADER,
|
|
121
|
+
export { AGENT_IP_HEADER, DISPATCH_HEADER, ExposifyManifest, type NextActionOptions, type NextAiReadyOptions, type NextMcpHandler, type NextPageOptions, agentIp, aiReady };
|