@frankieai/core 0.1.1 → 0.1.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.cjs +215 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +85 -1
- package/dist/index.d.ts +85 -1
- package/dist/index.js +213 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
agentConfigSchema: () => agentConfigSchema,
|
|
24
|
+
apiCapabilitySchema: () => apiCapabilitySchema,
|
|
24
25
|
apiSourceSchema: () => apiSourceSchema,
|
|
25
26
|
authConfigSchema: () => authConfigSchema,
|
|
26
27
|
brandingConfigSchema: () => brandingConfigSchema,
|
|
@@ -34,6 +35,7 @@ __export(index_exports, {
|
|
|
34
35
|
modelChoiceSchema: () => modelChoiceSchema,
|
|
35
36
|
modelConfigSchema: () => modelConfigSchema,
|
|
36
37
|
parseFrankieConfig: () => parseFrankieConfig,
|
|
38
|
+
planHostedInstall: () => planHostedInstall,
|
|
37
39
|
thinkingLevelSchema: () => thinkingLevelSchema,
|
|
38
40
|
toolPolicySchema: () => toolPolicySchema,
|
|
39
41
|
widgetThemeSchema: () => widgetThemeSchema
|
|
@@ -102,10 +104,20 @@ var featuresConfigSchema = import_zod.z.object({
|
|
|
102
104
|
enabled: import_zod.z.boolean().default(false),
|
|
103
105
|
/** The models offered to end users. The workspace default is always allowed. */
|
|
104
106
|
models: import_zod.z.array(modelChoiceSchema).default([])
|
|
105
|
-
}).prefault({})
|
|
107
|
+
}).prefault({}),
|
|
108
|
+
/**
|
|
109
|
+
* Platform onboarding: give the agent built-in tools to create and list the
|
|
110
|
+
* signed-in user's own Frankie workspaces. Only for a workspace that *is* the
|
|
111
|
+
* Frankie onboarding assistant — a normal customer never turns this on.
|
|
112
|
+
*/
|
|
113
|
+
workspaceAdmin: import_zod.z.boolean().default(false)
|
|
106
114
|
}).prefault({});
|
|
107
115
|
var mcpServerSchema = import_zod.z.object({
|
|
108
116
|
name: import_zod.z.string().min(1).regex(/^[a-z0-9][a-z0-9_-]*$/i, "MCP server names must be alphanumeric with - or _"),
|
|
117
|
+
enabled: import_zod.z.boolean().default(true),
|
|
118
|
+
/** Explicit tool selection. Omitted keeps legacy all-tools behavior; [] disables all. */
|
|
119
|
+
selectedTools: import_zod.z.array(import_zod.z.string().min(1)).optional(),
|
|
120
|
+
discoveredTools: import_zod.z.array(import_zod.z.object({ name: import_zod.z.string(), description: import_zod.z.string().optional() })).default([]),
|
|
109
121
|
/** `http` covers streamable HTTP; `sse` is the legacy transport. */
|
|
110
122
|
transport: import_zod.z.enum(["http", "sse"]).default("http"),
|
|
111
123
|
url: import_zod.z.string().url(),
|
|
@@ -120,6 +132,12 @@ var mcpServerSchema = import_zod.z.object({
|
|
|
120
132
|
/** Extra claims from the user token to forward as `x-frankie-<claim>`. */
|
|
121
133
|
forwardUserClaims: import_zod.z.array(import_zod.z.string()).default([])
|
|
122
134
|
});
|
|
135
|
+
var apiCapabilitySchema = import_zod.z.object({
|
|
136
|
+
operationId: import_zod.z.string().min(1),
|
|
137
|
+
method: import_zod.z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
|
|
138
|
+
path: import_zod.z.string().min(1),
|
|
139
|
+
summary: import_zod.z.string().optional()
|
|
140
|
+
});
|
|
123
141
|
var apiSourceSchema = import_zod.z.object({
|
|
124
142
|
name: import_zod.z.string().min(1).regex(/^[a-z0-9][a-z0-9_-]*$/i, "API source names must be alphanumeric with - or _"),
|
|
125
143
|
/** URL or local path to an OpenAPI 3.x document (JSON or YAML). */
|
|
@@ -141,6 +159,13 @@ var apiSourceSchema = import_zod.z.object({
|
|
|
141
159
|
* deliberately, per API.
|
|
142
160
|
*/
|
|
143
161
|
methods: import_zod.z.array(import_zod.z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"])).default(["GET"]),
|
|
162
|
+
enabled: import_zod.z.boolean().default(true),
|
|
163
|
+
/** Explicit operation selection, independent of the discovered inventory. */
|
|
164
|
+
selectedTools: import_zod.z.array(import_zod.z.string().min(1)).optional(),
|
|
165
|
+
/** Whether these approved API tools are also served by the workspace MCP. */
|
|
166
|
+
exposeAsMcp: import_zod.z.boolean().default(true),
|
|
167
|
+
/** A read-only inventory derived from the source for the dashboard. */
|
|
168
|
+
capabilities: import_zod.z.array(apiCapabilitySchema).default([]),
|
|
144
169
|
/** Static headers, e.g. a service token. */
|
|
145
170
|
headers: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).default({}),
|
|
146
171
|
/**
|
|
@@ -278,9 +303,197 @@ function humanise(name) {
|
|
|
278
303
|
function truncate(value, max) {
|
|
279
304
|
return value.length <= max ? value : `${value.slice(0, max - 1)}\u2026`;
|
|
280
305
|
}
|
|
306
|
+
|
|
307
|
+
// src/install.ts
|
|
308
|
+
function planHostedInstall(input) {
|
|
309
|
+
const { files, workspace } = input;
|
|
310
|
+
const layouts = Object.keys(files).filter((p) => /(?:^|\/)(?:src\/)?app(?:\/\([^/]+\))?\/layout\.tsx$/.test(p));
|
|
311
|
+
const rootLayoutPath = layouts.find((p) => !/(?:^|\/)app\/\([^/]+\)\/layout\.tsx$/.test(p)) ?? layouts[0];
|
|
312
|
+
if (!rootLayoutPath) throw new Error("Automatic installation supports Next.js App Router. No app/layout.tsx was found.");
|
|
313
|
+
const layoutPath = layouts.find((p) => /(?:^|\/)app\/\((?:dashboard|authenticated|protected|app)\)\/layout\.tsx$/.test(p)) ?? rootLayoutPath;
|
|
314
|
+
const appRoot = rootLayoutPath.slice(0, -"layout.tsx".length);
|
|
315
|
+
const integrationAppRoot = layoutPath.slice(0, -"layout.tsx".length);
|
|
316
|
+
const root = appRoot.replace(/(?:src\/)?app\/$/, "");
|
|
317
|
+
const pkgPath = root + "package.json";
|
|
318
|
+
const pkg = JSON.parse(files[pkgPath] ?? "{}");
|
|
319
|
+
if (!pkg.dependencies?.next) throw new Error("The selected app must have Next.js in dependencies.");
|
|
320
|
+
const out = /* @__PURE__ */ new Map();
|
|
321
|
+
const notes = [];
|
|
322
|
+
const existingMount = files[layoutPath].includes("FrankieWidgetMount");
|
|
323
|
+
const sourceRoot = appRoot.endsWith("src/app/") ? root + "src/" : root;
|
|
324
|
+
const firebase = Boolean(files[sourceRoot + "lib/firebase-admin.ts"]?.includes("getAdminAuth") && files[sourceRoot + "lib/session.ts"]?.includes("SESSION_COOKIE"));
|
|
325
|
+
const authFile = files[sourceRoot + "auth.ts"];
|
|
326
|
+
const authJs = Boolean(authFile?.includes("NextAuth") && /export\s+(?:const|let)\s*\{[^}]*\bauth\b/.test(authFile ?? ""));
|
|
327
|
+
const tokenPath = appRoot + "api/frankie/token/route.ts";
|
|
328
|
+
const needsAuthSetup = !workspace.allowAnonymous && !files[tokenPath] && !firebase && !authJs;
|
|
329
|
+
if (needsAuthSetup) notes.push("Connect getCurrentUser in the generated token endpoint to your server session. It rejects requests until configured.");
|
|
330
|
+
pkg.dependencies = { ...pkg.dependencies, "@frankieai/react": pkg.dependencies["@frankieai/react"] ?? "^0.1.5", ...!workspace.allowAnonymous ? { jose: pkg.dependencies.jose ?? "^6.0.11" } : {} };
|
|
331
|
+
out.set(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
332
|
+
const authProp = workspace.allowAnonymous ? "" : " getUserToken={getUserToken}";
|
|
333
|
+
const shared = `import { FrankieProvider, FrankieWidget, FrankieChatPage } from "@frankieai/react";
|
|
334
|
+
import "@frankieai/react/styles.css";
|
|
335
|
+
`;
|
|
336
|
+
const getToken = workspace.allowAnonymous ? "" : `
|
|
337
|
+
async function getUserToken() {
|
|
338
|
+
const response = await fetch("/api/frankie/token", { method: "POST", credentials: "same-origin" });
|
|
339
|
+
if (!response.ok) return undefined;
|
|
340
|
+
return ((await response.json()) as { token?: string }).token;
|
|
341
|
+
}
|
|
342
|
+
`;
|
|
343
|
+
let priorChat;
|
|
344
|
+
try {
|
|
345
|
+
const previous = JSON.parse(files[root + "frankie.install.json"] ?? "{}");
|
|
346
|
+
if (previous.workspaceId === workspace.id && ["/chat", "/frankie/chat"].includes(previous.chatPath)) priorChat = previous.chatPath;
|
|
347
|
+
} catch {
|
|
348
|
+
}
|
|
349
|
+
const chatPath = priorChat ?? (files[integrationAppRoot + "chat/page.tsx"] ? "/frankie/chat" : "/chat");
|
|
350
|
+
if (!priorChat && files[integrationAppRoot + chatPath.slice(1) + "/page.tsx"]) throw new Error("Both /chat and /frankie/chat already exist. Choose a chat route manually.");
|
|
351
|
+
out.set(root + "frankie.install.json", JSON.stringify({ workspaceId: workspace.id, chatPath }, null, 2) + "\n");
|
|
352
|
+
if (chatPath !== "/chat") notes.push("Existing /chat preserved. Frankie chat is available at /frankie/chat.");
|
|
353
|
+
const mount = `"use client";
|
|
354
|
+
import { usePathname } from "next/navigation";
|
|
355
|
+
${shared}${getToken}
|
|
356
|
+
export function FrankieWidgetMount() {
|
|
357
|
+
const pathname = usePathname();
|
|
358
|
+
const key = process.env.NEXT_PUBLIC_FRANKIE_KEY;
|
|
359
|
+
if (!key || pathname === ${JSON.stringify(chatPath)}) return null;
|
|
360
|
+
return <FrankieProvider publishableKey={key}${authProp}><FrankieWidget /></FrankieProvider>;
|
|
361
|
+
}
|
|
362
|
+
export function WorkspaceChatPage() {
|
|
363
|
+
const key = process.env.NEXT_PUBLIC_FRANKIE_KEY;
|
|
364
|
+
if (!key) return <p>Chat is not configured. Set NEXT_PUBLIC_FRANKIE_KEY in your deployment.</p>;
|
|
365
|
+
return <FrankieChatPage publishableKey={key}${authProp} />;
|
|
366
|
+
}
|
|
367
|
+
`;
|
|
368
|
+
const moduleName = existingMount ? "FrankieSetupChat" : "FrankieWidgetMount";
|
|
369
|
+
out.set(integrationAppRoot + "frankie/" + moduleName + ".tsx", mount);
|
|
370
|
+
if (!existingMount) {
|
|
371
|
+
const layout = files[layoutPath];
|
|
372
|
+
const importLine = `import { FrankieWidgetMount } from "./frankie/FrankieWidgetMount";
|
|
373
|
+
`;
|
|
374
|
+
const withImport = /^["']use client["'];/.test(layout) ? layout.replace(/^(['"]use client['"];)/, "$1\n" + importLine) : importLine + layout;
|
|
375
|
+
if (withImport.includes("</body>")) {
|
|
376
|
+
out.set(layoutPath, withImport.replace("</body>", " <FrankieWidgetMount />\n</body>"));
|
|
377
|
+
} else if (withImport.includes("</main>")) {
|
|
378
|
+
out.set(layoutPath, withImport.replace("</main>", " <FrankieWidgetMount />\n</main>"));
|
|
379
|
+
} else if (withImport.includes("{children}")) {
|
|
380
|
+
out.set(layoutPath, withImport.replace("{children}", "{children}<FrankieWidgetMount />"));
|
|
381
|
+
} else {
|
|
382
|
+
throw new Error("The selected layout has no safe widget mount point. Add FrankieWidgetMount manually.");
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
const pageImport = chatPath === "/chat" ? "../frankie/" : "../../frankie/";
|
|
386
|
+
out.set(integrationAppRoot + chatPath.slice(1) + "/page.tsx", `import { WorkspaceChatPage } from "${pageImport}${moduleName}";
|
|
387
|
+
export default WorkspaceChatPage;
|
|
388
|
+
`);
|
|
389
|
+
if (!workspace.allowAnonymous && !files[tokenPath]) {
|
|
390
|
+
const lookup = firebase ? `import { cookies } from "next/headers";
|
|
391
|
+
import { getAdminAuth } from "@/lib/firebase-admin";
|
|
392
|
+
import { SESSION_COOKIE } from "@/lib/session";
|
|
393
|
+
async function getCurrentUser() {
|
|
394
|
+
const cookie = (await cookies()).get(SESSION_COOKIE)?.value;
|
|
395
|
+
if (!cookie) return null;
|
|
396
|
+
const user = await getAdminAuth().verifySessionCookie(cookie, true);
|
|
397
|
+
return { id: user.uid, name: user.name, email: user.email };
|
|
398
|
+
}
|
|
399
|
+
` : authJs ? `import { auth } from "@/auth";
|
|
400
|
+
async function getCurrentUser() {
|
|
401
|
+
const session = await auth();
|
|
402
|
+
const user = session?.user as { id?: string; name?: string | null; email?: string | null } | undefined;
|
|
403
|
+
return user?.id ? user : null;
|
|
404
|
+
}
|
|
405
|
+
` : `// Replace with your app's verified server session lookup.
|
|
406
|
+
async function getCurrentUser(): Promise<{ id: string; name?: string; email?: string } | null> { return null; }
|
|
407
|
+
`;
|
|
408
|
+
out.set(tokenPath, `import { SignJWT } from "jose";
|
|
409
|
+
import { NextResponse } from "next/server";
|
|
410
|
+
${lookup}
|
|
411
|
+
export async function POST() {
|
|
412
|
+
const secret = process.env.FRANKIE_JWT_SECRET;
|
|
413
|
+
if (!secret) return NextResponse.json({ error: "Chat secret not configured" }, { status: 503 });
|
|
414
|
+
try {
|
|
415
|
+
const user = await getCurrentUser();
|
|
416
|
+
if (!user?.id) return NextResponse.json({ error: "Sign in required" }, { status: 401 });
|
|
417
|
+
const token = await new SignJWT({ name: user.name, email: user.email })
|
|
418
|
+
.setProtectedHeader({ alg: "HS256" }).setSubject(user.id).setIssuedAt()
|
|
419
|
+
.setExpirationTime("5m").sign(new TextEncoder().encode(secret));
|
|
420
|
+
return NextResponse.json({ token }, { headers: { "Cache-Control": "no-store" } });
|
|
421
|
+
} catch { return NextResponse.json({ error: "Sign in required" }, { status: 401 }); }
|
|
422
|
+
}
|
|
423
|
+
`);
|
|
424
|
+
}
|
|
425
|
+
let doc;
|
|
426
|
+
for (const [path, content] of Object.entries(files)) {
|
|
427
|
+
if (!/(?:openapi|swagger)[^/]*\.json$/i.test(path)) continue;
|
|
428
|
+
try {
|
|
429
|
+
const parsed = JSON.parse(content);
|
|
430
|
+
if (parsed.openapi && parsed.paths) {
|
|
431
|
+
doc = parsed;
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
} catch {
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const paths = {};
|
|
438
|
+
if (!doc) for (const [file, content] of Object.entries(files)) {
|
|
439
|
+
if (/\.(test|spec)\.[tj]sx?$/.test(file)) continue;
|
|
440
|
+
const nextRoute = file.match(/(?:^|\/)app(\/.*)\/route\.[tj]s$/);
|
|
441
|
+
if (nextRoute) {
|
|
442
|
+
const path = nextRoute[1].replace(/\/\([^/]+\)/g, "").replace(/\[([^\]]+)\]/g, "{$1}");
|
|
443
|
+
if (path.includes("...") || /\/(?:auth|frankie)(?:\/|$)/.test(path)) continue;
|
|
444
|
+
for (const method of ["GET"]) {
|
|
445
|
+
if (new RegExp("export\\s+(?:(?:async\\s+)?function\\s+" + method + "\\b|(?:const|let)\\s+" + method + "\\s*=)").test(content))
|
|
446
|
+
addInstallOperation(paths, method, path, file);
|
|
447
|
+
}
|
|
448
|
+
} else if (/\.[tj]s$/.test(file) && /(?:routes?|api)\//.test(file)) {
|
|
449
|
+
for (const match of content.matchAll(/\b(?:app|router|fastify)\.(get|post|put|patch|delete)(?:\s*<[^;]{1,1600}?>)?\s*\(\s*["']([^"']+)["']/g))
|
|
450
|
+
addInstallOperation(paths, match[1].toUpperCase(), match[2].replace(/:([A-Za-z0-9_]+)/g, "{$1}"), file);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
doc ??= { openapi: "3.0.3", info: { title: workspace.name + " API", version: "1.0.0", description: "Read-only generated route inventory. Review schemas, authorization, and the deployed origin before enabling tools." }, paths };
|
|
454
|
+
const specPath = root + "public/frankie.openapi.json";
|
|
455
|
+
const operationCount = Object.values(doc.paths).reduce((n, item) => n + Object.keys(item).filter((m) => /^(get|post|put|patch|delete)$/.test(m)).length, 0);
|
|
456
|
+
out.set(specPath, JSON.stringify(doc, null, 2) + "\n");
|
|
457
|
+
if (!operationCount) notes.push("No supported API routes detected. The OpenAPI draft has no operations; add the real schema before connecting tools.");
|
|
458
|
+
notes.push("Generated OpenAPI is read-only (GET only). Add write operations only to a reviewed, user-authorized schema.");
|
|
459
|
+
notes.push("Verify the public spec URL is reachable without dashboard authentication before connecting it.");
|
|
460
|
+
notes.push("The token endpoint issues a signed user JWT, but your product API must validate it and enforce user/resource permissions before exposing tools.");
|
|
461
|
+
const envPath = root + ".env.example";
|
|
462
|
+
const env = (files[envPath] ?? "").replace(/^NEXT_PUBLIC_FRANKIE_KEY=.*\n?/gm, "").replace(/^FRANKIE_JWT_SECRET=.*\n?/gm, "").trimEnd();
|
|
463
|
+
out.set(envPath, env + "\nNEXT_PUBLIC_FRANKIE_KEY=" + workspace.publishableKey + "\n" + (workspace.allowAnonymous ? "" : "# Server only: use the secret from this workspace. Never commit its value.\nFRANKIE_JWT_SECRET=\n"));
|
|
464
|
+
out.set(root + "FRANKIE_SETUP.md", `# Frankie setup
|
|
465
|
+
|
|
466
|
+
Workspace: ${workspace.name}
|
|
467
|
+
|
|
468
|
+
1. Add NEXT_PUBLIC_FRANKIE_KEY from .env.example to the frontend deployment.
|
|
469
|
+
${workspace.allowAnonymous ? "" : "2. Add FRANKIE_JWT_SECRET from this workspace to the server hosting /api/frankie/token. Never commit the value.\n"}3. Run your package manager and commit the updated lockfile (for pnpm: pnpm install --lockfile-only). The remote GitHub installer cannot safely synthesize a lockfile.
|
|
470
|
+
4. Confirm https://YOUR_APP/frankie.openapi.json is public. If your Next.js middleware protects all paths, explicitly bypass /frankie.openapi.json before connecting it.
|
|
471
|
+
5. Open ${chatPath}. It is created inside the detected authenticated dashboard group when one exists; add its link to the host sidebar during review.
|
|
472
|
+
6. Connect https://YOUR_APP/frankie.openapi.json in Frankie \u2192 Tools & MCP. The generated document contains only read-only GET operations.
|
|
473
|
+
7. Forwarding sends the Frankie-signed JWT. Your API must validate it and enforce user/resource permissions. It is not automatically interchangeable with a Firebase or OAuth access token.
|
|
474
|
+
8. Add NEXT_PUBLIC_FRANKIE_KEY to every frontend deployment environment (Preview and Production). ${workspace.allowAnonymous ? "" : "Add FRANKIE_JWT_SECRET only to the server environment running /api/frankie/token; never expose it to the browser.\n"}9. Review conversations in Workspace \u2192 Chat sessions.
|
|
475
|
+
|
|
476
|
+
## Remaining setup
|
|
477
|
+
|
|
478
|
+
${notes.map((n) => "- " + n).join("\n")}
|
|
479
|
+
`);
|
|
480
|
+
return { files: [...out].map(([path, content]) => ({ path, content })), root, chatPath, specPath, operationCount, needsAuthSetup, notes };
|
|
481
|
+
}
|
|
482
|
+
function addInstallOperation(paths, method, path, file) {
|
|
483
|
+
if (path.includes("*") || !path.startsWith("/")) return;
|
|
484
|
+
(paths[path] ??= {})[method.toLowerCase()] = {
|
|
485
|
+
operationId: (method.toLowerCase() + "_" + path).replace(/[^a-zA-Z0-9_]+/g, "_"),
|
|
486
|
+
summary: method + " " + path,
|
|
487
|
+
description: "Detected in " + file + ". Review schemas before enabling.",
|
|
488
|
+
parameters: [...path.matchAll(/\{([^}]+)\}/g)].map((m) => ({ name: m[1], in: "path", required: true, schema: { type: "string" } })),
|
|
489
|
+
responses: { "200": { description: "Successful response" } },
|
|
490
|
+
"x-frankie-review-required": true
|
|
491
|
+
};
|
|
492
|
+
}
|
|
281
493
|
// Annotate the CommonJS export names for ESM import in node:
|
|
282
494
|
0 && (module.exports = {
|
|
283
495
|
agentConfigSchema,
|
|
496
|
+
apiCapabilitySchema,
|
|
284
497
|
apiSourceSchema,
|
|
285
498
|
authConfigSchema,
|
|
286
499
|
brandingConfigSchema,
|
|
@@ -294,6 +507,7 @@ function truncate(value, max) {
|
|
|
294
507
|
modelChoiceSchema,
|
|
295
508
|
modelConfigSchema,
|
|
296
509
|
parseFrankieConfig,
|
|
510
|
+
planHostedInstall,
|
|
297
511
|
thinkingLevelSchema,
|
|
298
512
|
toolPolicySchema,
|
|
299
513
|
widgetThemeSchema
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/config.ts","../src/tools.ts"],"sourcesContent":["export * from \"./config.js\";\nexport * from \"./protocol.js\";\nexport * from \"./tools.js\";\n","import { z } from \"zod\";\n\n/**\n * Tenant configuration. One config == one embedded agent. Everything a\n * customer needs to change to make Frankie theirs lives here, so the code\n * itself stays generic.\n */\n\nexport const widgetThemeSchema = z\n .object({\n primary: z.string().optional(),\n primaryForeground: z.string().optional(),\n background: z.string().optional(),\n foreground: z.string().optional(),\n muted: z.string().optional(),\n border: z.string().optional(),\n radius: z.string().optional(),\n fontFamily: z.string().optional(),\n mode: z.enum([\"light\", \"dark\", \"system\"]).default(\"system\"),\n })\n .prefault({});\n\nexport const thinkingLevelSchema = z.enum([\"minimal\", \"low\", \"medium\", \"high\", \"xhigh\", \"max\"]);\n\nexport const modelConfigSchema = z.object({\n /**\n * Any provider the model layer knows about. `fireworks` is the default;\n * `anthropic`, `openai`, `google`, `groq`, `together`, `openrouter` and\n * ~30 others resolve too, which is what makes the model a config choice.\n */\n provider: z.string().min(1).default(\"fireworks\"),\n /**\n * Model id within that provider. The default is Kimi K2.7 on Fireworks,\n * which serves a native Anthropic-Messages endpoint and a 262K window.\n */\n model: z.string().default(\"accounts/fireworks/models/kimi-k2p7-code\"),\n /** Env var holding the provider key. Never put the key in config itself. */\n apiKeyEnv: z.string().default(\"FIREWORKS_API_KEY\"),\n /** Override the provider's base URL (self-hosted gateways, tests). */\n baseUrl: z.string().url().optional(),\n maxTokens: z.number().int().positive().max(200_000).default(8192),\n temperature: z.number().min(0).max(2).default(0.6),\n /**\n * Reasoning depth. Lowest by default: an onboarding widget is latency-\n * sensitive and its questions rarely need deliberation.\n */\n thinkingLevel: thinkingLevelSchema.default(\"minimal\"),\n /** Forward the model's reasoning stream to the client as thinking deltas. */\n streamReasoning: z.boolean().default(false),\n});\n\n/** One selectable model in the (optional) end-user model picker. */\nexport const modelChoiceSchema = z.object({\n /** Provider model id, e.g. \"accounts/fireworks/models/kimi-k2p7-code\". */\n id: z.string().min(1),\n /** Shown in the picker, e.g. \"Fast\" or \"Smart\". */\n label: z.string().min(1),\n /** Defaults to the workspace's default provider when omitted. */\n provider: z.string().optional(),\n});\n\n/**\n * Optional, opt-in chat capabilities the SDK consumer turns on per workspace.\n * Everything here is OFF by default: an embedded agent only gets a wider surface\n * when the customer deliberately enables it.\n */\nexport const featuresConfigSchema = z\n .object({\n /** Let end users attach files. Images go to a vision model; docs are read as text. */\n attachments: z\n .object({\n enabled: z.boolean().default(false),\n /** Accepted MIME patterns, e.g. \"image/*\", \"text/*\", \"application/json\". */\n accept: z.array(z.string()).default([\"image/*\", \"text/*\", \"application/json\"]),\n maxMb: z.number().positive().max(25).default(10),\n maxCount: z.number().int().positive().max(10).default(4),\n })\n .prefault({}),\n /** Let end users choose the model, from a curated list you control. */\n modelPicker: z\n .object({\n enabled: z.boolean().default(false),\n /** The models offered to end users. The workspace default is always allowed. */\n models: z.array(modelChoiceSchema).default([]),\n })\n .prefault({}),\n })\n .prefault({});\n\nexport const mcpServerSchema = z.object({\n name: z\n .string()\n .min(1)\n .regex(/^[a-z0-9][a-z0-9_-]*$/i, \"MCP server names must be alphanumeric with - or _\"),\n /** `http` covers streamable HTTP; `sse` is the legacy transport. */\n transport: z.enum([\"http\", \"sse\"]).default(\"http\"),\n url: z.string().url(),\n /** Static headers, e.g. a service token for the company's own MCP server. */\n headers: z.record(z.string(), z.string()).default({}),\n /**\n * When set, the verified end-user JWT is forwarded to the MCP server in this\n * header so the company's tools can act as that user. Use with care: only\n * enable it for MCP servers you control.\n */\n forwardUserTokenAs: z.string().optional(),\n /** Extra claims from the user token to forward as `x-frankie-<claim>`. */\n forwardUserClaims: z.array(z.string()).default([]),\n});\n\n/**\n * An HTTP API described by an OpenAPI document, turned into tools directly.\n *\n * This is the \"no MCP server to write\" path: point Frankie at a spec, name the\n * operations the agent may use, and it builds the tools at startup.\n */\nexport const apiSourceSchema = z.object({\n name: z\n .string()\n .min(1)\n .regex(/^[a-z0-9][a-z0-9_-]*$/i, \"API source names must be alphanumeric with - or _\"),\n /** URL or local path to an OpenAPI 3.x document (JSON or YAML). */\n spec: z.string().min(1),\n /** Overrides the spec's own `servers[0].url`. Required if the spec has none. */\n baseUrl: z.string().url().optional(),\n /**\n * Operation ids the agent may call. Empty means \"every operation that passes\n * the other filters\" — fine for a small internal API, risky for a large one.\n */\n operations: z.array(z.string()).default([]),\n /**\n * Path globs to include, e.g. `/projects/**`. Empty means all paths.\n */\n includePaths: z.array(z.string()).default([]),\n /**\n * HTTP methods the agent may use. Read-only by default: an onboarding agent\n * wired to a whole API should not be able to DELETE on its first day. Widen\n * deliberately, per API.\n */\n methods: z.array(z.enum([\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"])).default([\"GET\"]),\n /** Static headers, e.g. a service token. */\n headers: z.record(z.string(), z.string()).default({}),\n /**\n * When set, the verified end-user JWT is forwarded in this header, so your\n * API authorises the call exactly as it would any other request from them.\n */\n forwardUserTokenAs: z.string().optional(),\n /** Extra claims from the user token to forward as `x-frankie-<claim>`. */\n forwardUserClaims: z.array(z.string()).default([]),\n /** Per-call timeout. */\n timeoutMs: z.number().int().positive().default(30_000),\n /** Response bodies longer than this are truncated before the model sees them. */\n maxResponseChars: z.number().int().positive().default(8_000),\n});\n\nexport const toolPolicySchema = z.object({\n /**\n * Tool names the agent may call without asking. MCP tools are named\n * `mcp__<server>__<tool>` and generated API tools `api__<name>__<operation>`;\n * naming the prefix alone (`mcp__acme`, `api__acme`) allows all of them.\n */\n allow: z.array(z.string()).default([]),\n /** Explicit denials; evaluated before `allow`. */\n deny: z.array(z.string()).default([]),\n /**\n * Deny anything not in `allow`. On by default — an onboarding agent living\n * on a public marketing page should not have an open tool surface.\n */\n denyByDefault: z.boolean().default(true),\n});\n\nexport const agentConfigSchema = z.object({\n /** Prepended to the runtime's own operating instructions. */\n systemPrompt: z.string().default(\"\"),\n /** First message the widget shows before the user types anything. */\n greeting: z.string().default(\"Hi! I can walk you through getting set up. What are you trying to do?\"),\n suggestedPrompts: z.array(z.string()).max(6).default([]),\n maxTurns: z.number().int().positive().max(50).default(12),\n /** Hard stop on estimated spend per session. */\n maxBudgetUsd: z.number().positive().optional(),\n tools: toolPolicySchema.prefault({}),\n /** Show tool activity in the transcript. */\n showToolActivity: z.boolean().default(true),\n});\n\nexport const authConfigSchema = z.object({\n /**\n * Safe to ship in client bundles. Identifies the tenant; it authorises\n * nothing on its own — the origin allowlist and the user token do that.\n */\n publishableKey: z.string().min(8),\n /** Exact origins allowed to open sessions, e.g. `https://app.acme.com`. */\n allowedOrigins: z.array(z.string()).default([]),\n /** Let visitors chat without a host-signed token (marketing pages). */\n allowAnonymous: z.boolean().default(false),\n /** Env var holding the HS256 secret the host signs user tokens with. */\n jwtSecretEnv: z.string().default(\"FRANKIE_JWT_SECRET\"),\n /** Verify against a JWKS endpoint instead of a shared secret (RS/ES256). */\n jwksUrl: z.string().url().optional(),\n jwtIssuer: z.string().optional(),\n jwtAudience: z.string().optional(),\n /** How long an issued session token is good for. */\n sessionTtlSeconds: z.number().int().positive().default(60 * 60),\n /** Env var holding the secret used to sign Frankie's own session tokens. */\n sessionSecretEnv: z.string().default(\"FRANKIE_SESSION_SECRET\"),\n});\n\nexport const limitsConfigSchema = z.object({\n /** Messages per session per window. */\n messagesPerWindow: z.number().int().positive().default(30),\n windowSeconds: z.number().int().positive().default(15 * 60),\n /** Sessions a single IP may open per window. */\n sessionsPerIpPerWindow: z.number().int().positive().default(20),\n /** Longest a single turn may run before it is aborted. */\n turnTimeoutMs: z.number().int().positive().default(120_000),\n});\n\nexport const brandingConfigSchema = z.object({\n name: z.string().default(\"Assistant\"),\n tagline: z.string().optional(),\n avatarUrl: z.string().url().optional(),\n launcherLabel: z.string().default(\"Need a hand?\"),\n});\n\nexport const frankieConfigSchema = z.object({\n tenantId: z.string().min(1),\n branding: brandingConfigSchema.prefault({}),\n theme: widgetThemeSchema,\n model: modelConfigSchema.prefault({}),\n agent: agentConfigSchema.prefault({}),\n auth: authConfigSchema,\n mcpServers: z.array(mcpServerSchema).default([]),\n /** APIs turned into tools from their OpenAPI document. */\n apis: z.array(apiSourceSchema).default([]),\n limits: limitsConfigSchema.prefault({}),\n /** Opt-in end-user chat capabilities (attachments, model picker). */\n features: featuresConfigSchema,\n});\n\nexport type ThinkingLevel = z.infer<typeof thinkingLevelSchema>;\nexport type WidgetThemeConfig = z.infer<typeof widgetThemeSchema>;\nexport type ModelChoice = z.infer<typeof modelChoiceSchema>;\nexport type FeaturesConfig = z.infer<typeof featuresConfigSchema>;\nexport type ModelConfig = z.infer<typeof modelConfigSchema>;\nexport type McpServerConfig = z.infer<typeof mcpServerSchema>;\nexport type ApiSourceConfig = z.infer<typeof apiSourceSchema>;\nexport type ToolPolicy = z.infer<typeof toolPolicySchema>;\nexport type AgentConfig = z.infer<typeof agentConfigSchema>;\nexport type AuthConfig = z.infer<typeof authConfigSchema>;\nexport type LimitsConfig = z.infer<typeof limitsConfigSchema>;\nexport type BrandingConfig = z.infer<typeof brandingConfigSchema>;\nexport type FrankieConfig = z.infer<typeof frankieConfigSchema>;\nexport type FrankieConfigInput = z.input<typeof frankieConfigSchema>;\n\nexport function parseFrankieConfig(input: unknown): FrankieConfig {\n return frankieConfigSchema.parse(input);\n}\n\n/** Helper for `frankie.config.ts` files — gives editors full completion. */\nexport function defineFrankieConfig(config: FrankieConfigInput): FrankieConfigInput {\n return config;\n}\n","import type { ToolPolicy } from \"./config.js\";\n\nexport interface ToolDecision {\n allowed: boolean;\n reason: string;\n}\n\n/**\n * Decide whether the agent may run a tool.\n *\n * Patterns are matched exactly, or as a prefix when they end in `*`. MCP tools\n * arrive as `mcp__<server>__<tool>`, so listing `mcp__acme` (no trailing star)\n * also allows every tool on the `acme` server.\n */\nexport function evaluateToolPolicy(toolName: string, policy: ToolPolicy): ToolDecision {\n if (matchesAny(toolName, policy.deny)) {\n return { allowed: false, reason: `\"${toolName}\" is on the deny list for this agent.` };\n }\n if (matchesAny(toolName, policy.allow)) {\n return { allowed: true, reason: \"allowed by policy\" };\n }\n if (policy.denyByDefault) {\n return { allowed: false, reason: `\"${toolName}\" is not in this agent's allow list.` };\n }\n return { allowed: true, reason: \"no policy match; default allow\" };\n}\n\nfunction matchesAny(toolName: string, patterns: readonly string[]): boolean {\n return patterns.some((pattern) => matches(toolName, pattern));\n}\n\nfunction matches(toolName: string, pattern: string): boolean {\n if (pattern === \"*\") return true;\n if (pattern.endsWith(\"*\")) return toolName.startsWith(pattern.slice(0, -1));\n if (toolName === pattern) return true;\n // `mcp__acme` covers `mcp__acme__create_workspace`.\n return toolName.startsWith(`${pattern}__`);\n}\n\n/** A short human label for a tool call, for the \"Frankie is …\" line in the UI. */\nexport function describeTool(toolName: string, input: unknown): string {\n const mcp = /^mcp__([^_]+(?:_[^_]+)*)__(.+)$/.exec(toolName);\n if (mcp) {\n const [, server, tool] = mcp;\n return `${humanise(tool ?? toolName)} · ${server}`;\n }\n const inputRecord = typeof input === \"object\" && input !== null ? (input as Record<string, unknown>) : {};\n const hint = inputRecord[\"path\"] ?? inputRecord[\"url\"] ?? inputRecord[\"query\"] ?? inputRecord[\"command\"];\n return typeof hint === \"string\" ? `${humanise(toolName)}: ${truncate(hint, 60)}` : humanise(toolName);\n}\n\nfunction humanise(name: string): string {\n return name\n .replace(/[_-]+/g, \" \")\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .replace(/\\b\\w/g, (c) => c.toUpperCase())\n .trim();\n}\n\nfunction truncate(value: string, max: number): string {\n return value.length <= max ? value : `${value.slice(0, max - 1)}…`;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/config.ts","../src/tools.ts","../src/install.ts"],"sourcesContent":["export * from \"./config.js\";\nexport * from \"./protocol.js\";\nexport * from \"./tools.js\";\n\nexport * from \"./install.js\";\n","import { z } from \"zod\";\n\n/**\n * Tenant configuration. One config == one embedded agent. Everything a\n * customer needs to change to make Frankie theirs lives here, so the code\n * itself stays generic.\n */\n\nexport const widgetThemeSchema = z\n .object({\n primary: z.string().optional(),\n primaryForeground: z.string().optional(),\n background: z.string().optional(),\n foreground: z.string().optional(),\n muted: z.string().optional(),\n border: z.string().optional(),\n radius: z.string().optional(),\n fontFamily: z.string().optional(),\n mode: z.enum([\"light\", \"dark\", \"system\"]).default(\"system\"),\n })\n .prefault({});\n\nexport const thinkingLevelSchema = z.enum([\"minimal\", \"low\", \"medium\", \"high\", \"xhigh\", \"max\"]);\n\nexport const modelConfigSchema = z.object({\n /**\n * Any provider the model layer knows about. `fireworks` is the default;\n * `anthropic`, `openai`, `google`, `groq`, `together`, `openrouter` and\n * ~30 others resolve too, which is what makes the model a config choice.\n */\n provider: z.string().min(1).default(\"fireworks\"),\n /**\n * Model id within that provider. The default is Kimi K2.7 on Fireworks,\n * which serves a native Anthropic-Messages endpoint and a 262K window.\n */\n model: z.string().default(\"accounts/fireworks/models/kimi-k2p7-code\"),\n /** Env var holding the provider key. Never put the key in config itself. */\n apiKeyEnv: z.string().default(\"FIREWORKS_API_KEY\"),\n /** Override the provider's base URL (self-hosted gateways, tests). */\n baseUrl: z.string().url().optional(),\n maxTokens: z.number().int().positive().max(200_000).default(8192),\n temperature: z.number().min(0).max(2).default(0.6),\n /**\n * Reasoning depth. Lowest by default: an onboarding widget is latency-\n * sensitive and its questions rarely need deliberation.\n */\n thinkingLevel: thinkingLevelSchema.default(\"minimal\"),\n /** Forward the model's reasoning stream to the client as thinking deltas. */\n streamReasoning: z.boolean().default(false),\n});\n\n/** One selectable model in the (optional) end-user model picker. */\nexport const modelChoiceSchema = z.object({\n /** Provider model id, e.g. \"accounts/fireworks/models/kimi-k2p7-code\". */\n id: z.string().min(1),\n /** Shown in the picker, e.g. \"Fast\" or \"Smart\". */\n label: z.string().min(1),\n /** Defaults to the workspace's default provider when omitted. */\n provider: z.string().optional(),\n});\n\n/**\n * Optional, opt-in chat capabilities the SDK consumer turns on per workspace.\n * Everything here is OFF by default: an embedded agent only gets a wider surface\n * when the customer deliberately enables it.\n */\nexport const featuresConfigSchema = z\n .object({\n /** Let end users attach files. Images go to a vision model; docs are read as text. */\n attachments: z\n .object({\n enabled: z.boolean().default(false),\n /** Accepted MIME patterns, e.g. \"image/*\", \"text/*\", \"application/json\". */\n accept: z.array(z.string()).default([\"image/*\", \"text/*\", \"application/json\"]),\n maxMb: z.number().positive().max(25).default(10),\n maxCount: z.number().int().positive().max(10).default(4),\n })\n .prefault({}),\n /** Let end users choose the model, from a curated list you control. */\n modelPicker: z\n .object({\n enabled: z.boolean().default(false),\n /** The models offered to end users. The workspace default is always allowed. */\n models: z.array(modelChoiceSchema).default([]),\n })\n .prefault({}),\n /**\n * Platform onboarding: give the agent built-in tools to create and list the\n * signed-in user's own Frankie workspaces. Only for a workspace that *is* the\n * Frankie onboarding assistant — a normal customer never turns this on.\n */\n workspaceAdmin: z.boolean().default(false),\n })\n .prefault({});\n\nexport const mcpServerSchema = z.object({\n name: z\n .string()\n .min(1)\n .regex(/^[a-z0-9][a-z0-9_-]*$/i, \"MCP server names must be alphanumeric with - or _\"),\n enabled: z.boolean().default(true),\n /** Explicit tool selection. Omitted keeps legacy all-tools behavior; [] disables all. */\n selectedTools: z.array(z.string().min(1)).optional(),\n discoveredTools: z.array(z.object({ name: z.string(), description: z.string().optional() })).default([]),\n /** `http` covers streamable HTTP; `sse` is the legacy transport. */\n transport: z.enum([\"http\", \"sse\"]).default(\"http\"),\n url: z.string().url(),\n /** Static headers, e.g. a service token for the company's own MCP server. */\n headers: z.record(z.string(), z.string()).default({}),\n /**\n * When set, the verified end-user JWT is forwarded to the MCP server in this\n * header so the company's tools can act as that user. Use with care: only\n * enable it for MCP servers you control.\n */\n forwardUserTokenAs: z.string().optional(),\n /** Extra claims from the user token to forward as `x-frankie-<claim>`. */\n forwardUserClaims: z.array(z.string()).default([]),\n});\n\n/**\n * An HTTP API described by an OpenAPI document, turned into tools directly.\n *\n * This is the \"no MCP server to write\" path: point Frankie at a spec, name the\n * operations the agent may use, and it builds the tools at startup.\n */\nexport const apiCapabilitySchema = z.object({\n operationId: z.string().min(1),\n method: z.enum([\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"]),\n path: z.string().min(1),\n summary: z.string().optional(),\n});\n\nexport const apiSourceSchema = z.object({\n name: z\n .string()\n .min(1)\n .regex(/^[a-z0-9][a-z0-9_-]*$/i, \"API source names must be alphanumeric with - or _\"),\n /** URL or local path to an OpenAPI 3.x document (JSON or YAML). */\n spec: z.string().min(1),\n /** Overrides the spec's own `servers[0].url`. Required if the spec has none. */\n baseUrl: z.string().url().optional(),\n /**\n * Operation ids the agent may call. Empty means \"every operation that passes\n * the other filters\" — fine for a small internal API, risky for a large one.\n */\n operations: z.array(z.string()).default([]),\n /**\n * Path globs to include, e.g. `/projects/**`. Empty means all paths.\n */\n includePaths: z.array(z.string()).default([]),\n /**\n * HTTP methods the agent may use. Read-only by default: an onboarding agent\n * wired to a whole API should not be able to DELETE on its first day. Widen\n * deliberately, per API.\n */\n methods: z.array(z.enum([\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"])).default([\"GET\"]),\n enabled: z.boolean().default(true),\n /** Explicit operation selection, independent of the discovered inventory. */\n selectedTools: z.array(z.string().min(1)).optional(),\n /** Whether these approved API tools are also served by the workspace MCP. */\n exposeAsMcp: z.boolean().default(true),\n /** A read-only inventory derived from the source for the dashboard. */\n capabilities: z.array(apiCapabilitySchema).default([]),\n /** Static headers, e.g. a service token. */\n headers: z.record(z.string(), z.string()).default({}),\n /**\n * When set, the verified end-user JWT is forwarded in this header, so your\n * API authorises the call exactly as it would any other request from them.\n */\n forwardUserTokenAs: z.string().optional(),\n /** Extra claims from the user token to forward as `x-frankie-<claim>`. */\n forwardUserClaims: z.array(z.string()).default([]),\n /** Per-call timeout. */\n timeoutMs: z.number().int().positive().default(30_000),\n /** Response bodies longer than this are truncated before the model sees them. */\n maxResponseChars: z.number().int().positive().default(8_000),\n});\n\nexport const toolPolicySchema = z.object({\n /**\n * Tool names the agent may call without asking. MCP tools are named\n * `mcp__<server>__<tool>` and generated API tools `api__<name>__<operation>`;\n * naming the prefix alone (`mcp__acme`, `api__acme`) allows all of them.\n */\n allow: z.array(z.string()).default([]),\n /** Explicit denials; evaluated before `allow`. */\n deny: z.array(z.string()).default([]),\n /**\n * Deny anything not in `allow`. On by default — an onboarding agent living\n * on a public marketing page should not have an open tool surface.\n */\n denyByDefault: z.boolean().default(true),\n});\n\nexport const agentConfigSchema = z.object({\n /** Prepended to the runtime's own operating instructions. */\n systemPrompt: z.string().default(\"\"),\n /** First message the widget shows before the user types anything. */\n greeting: z.string().default(\"Hi! I can walk you through getting set up. What are you trying to do?\"),\n suggestedPrompts: z.array(z.string()).max(6).default([]),\n maxTurns: z.number().int().positive().max(50).default(12),\n /** Hard stop on estimated spend per session. */\n maxBudgetUsd: z.number().positive().optional(),\n tools: toolPolicySchema.prefault({}),\n /** Show tool activity in the transcript. */\n showToolActivity: z.boolean().default(true),\n});\n\nexport const authConfigSchema = z.object({\n /**\n * Safe to ship in client bundles. Identifies the tenant; it authorises\n * nothing on its own — the origin allowlist and the user token do that.\n */\n publishableKey: z.string().min(8),\n /** Exact origins allowed to open sessions, e.g. `https://app.acme.com`. */\n allowedOrigins: z.array(z.string()).default([]),\n /** Let visitors chat without a host-signed token (marketing pages). */\n allowAnonymous: z.boolean().default(false),\n /** Env var holding the HS256 secret the host signs user tokens with. */\n jwtSecretEnv: z.string().default(\"FRANKIE_JWT_SECRET\"),\n /** Verify against a JWKS endpoint instead of a shared secret (RS/ES256). */\n jwksUrl: z.string().url().optional(),\n jwtIssuer: z.string().optional(),\n jwtAudience: z.string().optional(),\n /** How long an issued session token is good for. */\n sessionTtlSeconds: z.number().int().positive().default(60 * 60),\n /** Env var holding the secret used to sign Frankie's own session tokens. */\n sessionSecretEnv: z.string().default(\"FRANKIE_SESSION_SECRET\"),\n});\n\nexport const limitsConfigSchema = z.object({\n /** Messages per session per window. */\n messagesPerWindow: z.number().int().positive().default(30),\n windowSeconds: z.number().int().positive().default(15 * 60),\n /** Sessions a single IP may open per window. */\n sessionsPerIpPerWindow: z.number().int().positive().default(20),\n /** Longest a single turn may run before it is aborted. */\n turnTimeoutMs: z.number().int().positive().default(120_000),\n});\n\nexport const brandingConfigSchema = z.object({\n name: z.string().default(\"Assistant\"),\n tagline: z.string().optional(),\n avatarUrl: z.string().url().optional(),\n launcherLabel: z.string().default(\"Need a hand?\"),\n});\n\nexport const frankieConfigSchema = z.object({\n tenantId: z.string().min(1),\n branding: brandingConfigSchema.prefault({}),\n theme: widgetThemeSchema,\n model: modelConfigSchema.prefault({}),\n agent: agentConfigSchema.prefault({}),\n auth: authConfigSchema,\n mcpServers: z.array(mcpServerSchema).default([]),\n /** APIs turned into tools from their OpenAPI document. */\n apis: z.array(apiSourceSchema).default([]),\n limits: limitsConfigSchema.prefault({}),\n /** Opt-in end-user chat capabilities (attachments, model picker). */\n features: featuresConfigSchema,\n});\n\nexport type ThinkingLevel = z.infer<typeof thinkingLevelSchema>;\nexport type WidgetThemeConfig = z.infer<typeof widgetThemeSchema>;\nexport type ModelChoice = z.infer<typeof modelChoiceSchema>;\nexport type FeaturesConfig = z.infer<typeof featuresConfigSchema>;\nexport type ModelConfig = z.infer<typeof modelConfigSchema>;\nexport type McpServerConfig = z.infer<typeof mcpServerSchema>;\nexport type ApiCapability = z.infer<typeof apiCapabilitySchema>;\nexport type ApiSourceConfig = z.infer<typeof apiSourceSchema>;\nexport type ToolPolicy = z.infer<typeof toolPolicySchema>;\nexport type AgentConfig = z.infer<typeof agentConfigSchema>;\nexport type AuthConfig = z.infer<typeof authConfigSchema>;\nexport type LimitsConfig = z.infer<typeof limitsConfigSchema>;\nexport type BrandingConfig = z.infer<typeof brandingConfigSchema>;\nexport type FrankieConfig = z.infer<typeof frankieConfigSchema>;\nexport type FrankieConfigInput = z.input<typeof frankieConfigSchema>;\n\nexport function parseFrankieConfig(input: unknown): FrankieConfig {\n return frankieConfigSchema.parse(input);\n}\n\n/** Helper for `frankie.config.ts` files — gives editors full completion. */\nexport function defineFrankieConfig(config: FrankieConfigInput): FrankieConfigInput {\n return config;\n}\n","import type { ToolPolicy } from \"./config.js\";\n\nexport interface ToolDecision {\n allowed: boolean;\n reason: string;\n}\n\n/**\n * Decide whether the agent may run a tool.\n *\n * Patterns are matched exactly, or as a prefix when they end in `*`. MCP tools\n * arrive as `mcp__<server>__<tool>`, so listing `mcp__acme` (no trailing star)\n * also allows every tool on the `acme` server.\n */\nexport function evaluateToolPolicy(toolName: string, policy: ToolPolicy): ToolDecision {\n if (matchesAny(toolName, policy.deny)) {\n return { allowed: false, reason: `\"${toolName}\" is on the deny list for this agent.` };\n }\n if (matchesAny(toolName, policy.allow)) {\n return { allowed: true, reason: \"allowed by policy\" };\n }\n if (policy.denyByDefault) {\n return { allowed: false, reason: `\"${toolName}\" is not in this agent's allow list.` };\n }\n return { allowed: true, reason: \"no policy match; default allow\" };\n}\n\nfunction matchesAny(toolName: string, patterns: readonly string[]): boolean {\n return patterns.some((pattern) => matches(toolName, pattern));\n}\n\nfunction matches(toolName: string, pattern: string): boolean {\n if (pattern === \"*\") return true;\n if (pattern.endsWith(\"*\")) return toolName.startsWith(pattern.slice(0, -1));\n if (toolName === pattern) return true;\n // `mcp__acme` covers `mcp__acme__create_workspace`.\n return toolName.startsWith(`${pattern}__`);\n}\n\n/** A short human label for a tool call, for the \"Frankie is …\" line in the UI. */\nexport function describeTool(toolName: string, input: unknown): string {\n const mcp = /^mcp__([^_]+(?:_[^_]+)*)__(.+)$/.exec(toolName);\n if (mcp) {\n const [, server, tool] = mcp;\n return `${humanise(tool ?? toolName)} · ${server}`;\n }\n const inputRecord = typeof input === \"object\" && input !== null ? (input as Record<string, unknown>) : {};\n const hint = inputRecord[\"path\"] ?? inputRecord[\"url\"] ?? inputRecord[\"query\"] ?? inputRecord[\"command\"];\n return typeof hint === \"string\" ? `${humanise(toolName)}: ${truncate(hint, 60)}` : humanise(toolName);\n}\n\nfunction humanise(name: string): string {\n return name\n .replace(/[_-]+/g, \" \")\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .replace(/\\b\\w/g, (c) => c.toUpperCase())\n .trim();\n}\n\nfunction truncate(value: string, max: number): string {\n return value.length <= max ? value : `${value.slice(0, max - 1)}…`;\n}\n","/** One installation plan for GitHub and the local CLI. Never includes credentials. */\nexport interface InstallProject {\n files: Record<string, string>;\n workspace: { id: string; name: string; publishableKey: string; allowAnonymous: boolean };\n apiBaseUrl?: string;\n}\nexport interface InstallPlan {\n files: Array<{ path: string; content: string }>; root: string; chatPath: string; specPath: string;\n operationCount: number; needsAuthSetup: boolean; notes: string[];\n}\ntype ApiDocument = { paths: Record<string, Record<string, unknown>>; [key: string]: unknown };\nexport function planHostedInstall(input: InstallProject): InstallPlan {\n const { files, workspace } = input;\n const layouts = Object.keys(files).filter((p) => /(?:^|\\/)(?:src\\/)?app(?:\\/\\([^/]+\\))?\\/layout\\.tsx$/.test(p));\n const rootLayoutPath = layouts.find((p) => !/(?:^|\\/)app\\/\\([^/]+\\)\\/layout\\.tsx$/.test(p)) ?? layouts[0];\n if (!rootLayoutPath) throw new Error(\"Automatic installation supports Next.js App Router. No app/layout.tsx was found.\");\n // Prefer an existing authenticated/dashboard route group for the chat page and\n // floating widget; route groups do not change the URL, but preserve its layout,\n // session gate, and sidebar.\n const layoutPath = layouts.find((p) => /(?:^|\\/)app\\/\\((?:dashboard|authenticated|protected|app)\\)\\/layout\\.tsx$/.test(p)) ?? rootLayoutPath;\n const appRoot = rootLayoutPath.slice(0, -\"layout.tsx\".length);\n const integrationAppRoot = layoutPath.slice(0, -\"layout.tsx\".length);\n const root = appRoot.replace(/(?:src\\/)?app\\/$/, \"\");\n const pkgPath = root + \"package.json\";\n const pkg = JSON.parse(files[pkgPath] ?? \"{}\");\n if (!pkg.dependencies?.next) throw new Error(\"The selected app must have Next.js in dependencies.\");\n const out = new Map<string, string>();\n const notes: string[] = [];\n const existingMount = files[layoutPath]!.includes(\"FrankieWidgetMount\");\n const sourceRoot = appRoot.endsWith(\"src/app/\") ? root + \"src/\" : root;\n const firebase = Boolean(files[sourceRoot + \"lib/firebase-admin.ts\"]?.includes(\"getAdminAuth\") && files[sourceRoot + \"lib/session.ts\"]?.includes(\"SESSION_COOKIE\"));\n const authFile = files[sourceRoot + \"auth.ts\"];\n const authJs = Boolean(authFile?.includes(\"NextAuth\") && /export\\s+(?:const|let)\\s*\\{[^}]*\\bauth\\b/.test(authFile ?? \"\"));\n const tokenPath = appRoot + \"api/frankie/token/route.ts\";\n const needsAuthSetup = !workspace.allowAnonymous && !files[tokenPath] && !firebase && !authJs;\n if (needsAuthSetup) notes.push(\"Connect getCurrentUser in the generated token endpoint to your server session. It rejects requests until configured.\");\n pkg.dependencies = { ...pkg.dependencies, \"@frankieai/react\": pkg.dependencies[\"@frankieai/react\"] ?? \"^0.1.5\", ...(!workspace.allowAnonymous ? { jose: pkg.dependencies.jose ?? \"^6.0.11\" } : {}) };\n out.set(pkgPath, JSON.stringify(pkg, null, 2) + \"\\n\");\n const authProp = workspace.allowAnonymous ? \"\" : \" getUserToken={getUserToken}\";\n // The distributed stylesheet omits Preflight and scopes every rule to .frankie-root,\n // so the full-page chat is polished without changing the host dashboard.\n const shared = `import { FrankieProvider, FrankieWidget, FrankieChatPage } from \"@frankieai/react\";\\nimport \"@frankieai/react/styles.css\";\\n`;\n const getToken = workspace.allowAnonymous ? \"\" : `\nasync function getUserToken() {\n const response = await fetch(\"/api/frankie/token\", { method: \"POST\", credentials: \"same-origin\" });\n if (!response.ok) return undefined;\n return ((await response.json()) as { token?: string }).token;\n}\n`;\n let priorChat: string | undefined;\n try { const previous = JSON.parse(files[root + \"frankie.install.json\"] ?? \"{}\"); if (previous.workspaceId === workspace.id && [\"/chat\", \"/frankie/chat\"].includes(previous.chatPath)) priorChat = previous.chatPath; } catch {}\n const chatPath = priorChat ?? (files[integrationAppRoot + \"chat/page.tsx\"] ? \"/frankie/chat\" : \"/chat\");\n if (!priorChat && files[integrationAppRoot + chatPath.slice(1) + \"/page.tsx\"]) throw new Error(\"Both /chat and /frankie/chat already exist. Choose a chat route manually.\");\n out.set(root + \"frankie.install.json\", JSON.stringify({ workspaceId: workspace.id, chatPath }, null, 2) + \"\\n\");\n if (chatPath !== \"/chat\") notes.push(\"Existing /chat preserved. Frankie chat is available at /frankie/chat.\");\n const mount = `\"use client\";\\nimport { usePathname } from \"next/navigation\";\\n${shared}${getToken}\nexport function FrankieWidgetMount() {\n const pathname = usePathname();\n const key = process.env.NEXT_PUBLIC_FRANKIE_KEY;\n if (!key || pathname === ${JSON.stringify(chatPath)}) return null;\n return <FrankieProvider publishableKey={key}${authProp}><FrankieWidget /></FrankieProvider>;\n}\nexport function WorkspaceChatPage() {\n const key = process.env.NEXT_PUBLIC_FRANKIE_KEY;\n if (!key) return <p>Chat is not configured. Set NEXT_PUBLIC_FRANKIE_KEY in your deployment.</p>;\n return <FrankieChatPage publishableKey={key}${authProp} />;\n}\n`;\n const moduleName = existingMount ? \"FrankieSetupChat\" : \"FrankieWidgetMount\";\n out.set(integrationAppRoot + \"frankie/\" + moduleName + \".tsx\", mount);\n if (!existingMount) {\n const layout = files[layoutPath]!;\n const importLine = `import { FrankieWidgetMount } from \"./frankie/FrankieWidgetMount\";\\n`;\n const withImport = /^[\"']use client[\"'];/.test(layout) ? layout.replace(/^(['\"]use client['\"];)/, \"$1\\n\" + importLine) : importLine + layout;\n if (withImport.includes(\"</body>\")) {\n out.set(layoutPath, withImport.replace(\"</body>\", \" <FrankieWidgetMount />\\n</body>\"));\n } else if (withImport.includes(\"</main>\")) {\n out.set(layoutPath, withImport.replace(\"</main>\", \" <FrankieWidgetMount />\\n</main>\"));\n } else if (withImport.includes(\"{children}\")) {\n out.set(layoutPath, withImport.replace(\"{children}\", \"{children}<FrankieWidgetMount />\"));\n } else {\n throw new Error(\"The selected layout has no safe widget mount point. Add FrankieWidgetMount manually.\");\n }\n }\n const pageImport = chatPath === \"/chat\" ? \"../frankie/\" : \"../../frankie/\";\n out.set(integrationAppRoot + chatPath.slice(1) + \"/page.tsx\", `import { WorkspaceChatPage } from \"${pageImport}${moduleName}\";\\nexport default WorkspaceChatPage;\\n`);\n if (!workspace.allowAnonymous && !files[tokenPath]) {\n const lookup = firebase\n ? `import { cookies } from \"next/headers\";\\nimport { getAdminAuth } from \"@/lib/firebase-admin\";\\nimport { SESSION_COOKIE } from \"@/lib/session\";\\nasync function getCurrentUser() {\\n const cookie = (await cookies()).get(SESSION_COOKIE)?.value;\\n if (!cookie) return null;\\n const user = await getAdminAuth().verifySessionCookie(cookie, true);\\n return { id: user.uid, name: user.name, email: user.email };\\n}\\n`\n : authJs ? `import { auth } from \"@/auth\";\\nasync function getCurrentUser() {\\n const session = await auth();\\n const user = session?.user as { id?: string; name?: string | null; email?: string | null } | undefined;\\n return user?.id ? user : null;\\n}\\n`\n : `// Replace with your app's verified server session lookup.\\nasync function getCurrentUser(): Promise<{ id: string; name?: string; email?: string } | null> { return null; }\\n`;\n out.set(tokenPath, `import { SignJWT } from \"jose\";\\nimport { NextResponse } from \"next/server\";\\n${lookup}\nexport async function POST() {\n const secret = process.env.FRANKIE_JWT_SECRET;\n if (!secret) return NextResponse.json({ error: \"Chat secret not configured\" }, { status: 503 });\n try {\n const user = await getCurrentUser();\n if (!user?.id) return NextResponse.json({ error: \"Sign in required\" }, { status: 401 });\n const token = await new SignJWT({ name: user.name, email: user.email })\n .setProtectedHeader({ alg: \"HS256\" }).setSubject(user.id).setIssuedAt()\n .setExpirationTime(\"5m\").sign(new TextEncoder().encode(secret));\n return NextResponse.json({ token }, { headers: { \"Cache-Control\": \"no-store\" } });\n } catch { return NextResponse.json({ error: \"Sign in required\" }, { status: 401 }); }\n}\n`);\n }\n let doc: ApiDocument | undefined;\n for (const [path, content] of Object.entries(files)) {\n if (!/(?:openapi|swagger)[^/]*\\.json$/i.test(path)) continue;\n try { const parsed = JSON.parse(content); if (parsed.openapi && parsed.paths) { doc = parsed; break; } } catch {}\n }\n const paths: Record<string, Record<string, unknown>> = {};\n if (!doc) for (const [file, content] of Object.entries(files)) {\n if (/\\.(test|spec)\\.[tj]sx?$/.test(file)) continue;\n const nextRoute = file.match(/(?:^|\\/)app(\\/.*)\\/route\\.[tj]s$/);\n if (nextRoute) {\n const path = nextRoute[1]!.replace(/\\/\\([^/]+\\)/g, \"\").replace(/\\[([^\\]]+)\\]/g, \"{$1}\");\n if (path.includes(\"...\") || /\\/(?:auth|frankie)(?:\\/|$)/.test(path)) continue;\n // Generated tool inventories are read-only. Writes require an explicit, reviewed schema.\n for (const method of [\"GET\"]) {\n if (new RegExp(\"export\\\\s+(?:(?:async\\\\s+)?function\\\\s+\" + method + \"\\\\b|(?:const|let)\\\\s+\" + method + \"\\\\s*=)\").test(content))\n addInstallOperation(paths, method, path, file);\n }\n } else if (/\\.[tj]s$/.test(file) && /(?:routes?|api)\\//.test(file)) {\n for (const match of content.matchAll(/\\b(?:app|router|fastify)\\.(get|post|put|patch|delete)(?:\\s*<[^;]{1,1600}?>)?\\s*\\(\\s*[\"']([^\"']+)[\"']/g))\n addInstallOperation(paths, match[1]!.toUpperCase(), match[2]!.replace(/:([A-Za-z0-9_]+)/g, \"{$1}\"), file);\n }\n }\n doc ??= { openapi: \"3.0.3\", info: { title: workspace.name + \" API\", version: \"1.0.0\", description: \"Read-only generated route inventory. Review schemas, authorization, and the deployed origin before enabling tools.\" }, paths };\n const specPath = root + \"public/frankie.openapi.json\";\n const operationCount = Object.values(doc.paths).reduce((n, item) => n + Object.keys(item).filter((m) => /^(get|post|put|patch|delete)$/.test(m)).length, 0);\n out.set(specPath, JSON.stringify(doc, null, 2) + \"\\n\");\n if (!operationCount) notes.push(\"No supported API routes detected. The OpenAPI draft has no operations; add the real schema before connecting tools.\");\n notes.push(\"Generated OpenAPI is read-only (GET only). Add write operations only to a reviewed, user-authorized schema.\");\n notes.push(\"Verify the public spec URL is reachable without dashboard authentication before connecting it.\");\n notes.push(\"The token endpoint issues a signed user JWT, but your product API must validate it and enforce user/resource permissions before exposing tools.\");\n const envPath = root + \".env.example\";\n const env = (files[envPath] ?? \"\").replace(/^NEXT_PUBLIC_FRANKIE_KEY=.*\\n?/gm, \"\").replace(/^FRANKIE_JWT_SECRET=.*\\n?/gm, \"\").trimEnd();\n out.set(envPath, env + \"\\nNEXT_PUBLIC_FRANKIE_KEY=\" + workspace.publishableKey + \"\\n\" + (workspace.allowAnonymous ? \"\" : \"# Server only: use the secret from this workspace. Never commit its value.\\nFRANKIE_JWT_SECRET=\\n\"));\n out.set(root + \"FRANKIE_SETUP.md\", `# Frankie setup\\n\\nWorkspace: ${workspace.name}\\n\\n1. Add NEXT_PUBLIC_FRANKIE_KEY from .env.example to the frontend deployment.\\n${workspace.allowAnonymous ? \"\" : \"2. Add FRANKIE_JWT_SECRET from this workspace to the server hosting /api/frankie/token. Never commit the value.\\n\"}3. Run your package manager and commit the updated lockfile (for pnpm: pnpm install --lockfile-only). The remote GitHub installer cannot safely synthesize a lockfile.\\n4. Confirm https://YOUR_APP/frankie.openapi.json is public. If your Next.js middleware protects all paths, explicitly bypass /frankie.openapi.json before connecting it.\\n5. Open ${chatPath}. It is created inside the detected authenticated dashboard group when one exists; add its link to the host sidebar during review.\\n6. Connect https://YOUR_APP/frankie.openapi.json in Frankie → Tools & MCP. The generated document contains only read-only GET operations.\\n7. Forwarding sends the Frankie-signed JWT. Your API must validate it and enforce user/resource permissions. It is not automatically interchangeable with a Firebase or OAuth access token.\\n8. Add NEXT_PUBLIC_FRANKIE_KEY to every frontend deployment environment (Preview and Production). ${workspace.allowAnonymous ? \"\" : \"Add FRANKIE_JWT_SECRET only to the server environment running /api/frankie/token; never expose it to the browser.\\n\"}9. Review conversations in Workspace → Chat sessions.\\n\\n## Remaining setup\\n\\n${notes.map((n) => \"- \" + n).join(\"\\n\")}\\n`);\n return { files: [...out].map(([path, content]) => ({ path, content })), root, chatPath, specPath, operationCount, needsAuthSetup, notes };\n}\nfunction addInstallOperation(paths: Record<string, Record<string, unknown>>, method: string, path: string, file: string) {\n if (path.includes(\"*\") || !path.startsWith(\"/\")) return;\n (paths[path] ??= {})[method.toLowerCase()] = {\n operationId: (method.toLowerCase() + \"_\" + path).replace(/[^a-zA-Z0-9_]+/g, \"_\"),\n summary: method + \" \" + path, description: \"Detected in \" + file + \". Review schemas before enabling.\",\n parameters: [...path.matchAll(/\\{([^}]+)\\}/g)].map((m) => ({ name: m[1], in: \"path\", required: true, schema: { type: \"string\" } })),\n responses: { \"200\": { description: \"Successful response\" } }, \"x-frankie-review-required\": true,\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -71,9 +71,16 @@ declare const featuresConfigSchema: z.ZodPrefault<z.ZodObject<{
|
|
|
71
71
|
provider: z.ZodOptional<z.ZodString>;
|
|
72
72
|
}, z.core.$strip>>>;
|
|
73
73
|
}, z.core.$strip>>;
|
|
74
|
+
workspaceAdmin: z.ZodDefault<z.ZodBoolean>;
|
|
74
75
|
}, z.core.$strip>>;
|
|
75
76
|
declare const mcpServerSchema: z.ZodObject<{
|
|
76
77
|
name: z.ZodString;
|
|
78
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
79
|
+
selectedTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
80
|
+
discoveredTools: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
81
|
+
name: z.ZodString;
|
|
82
|
+
description: z.ZodOptional<z.ZodString>;
|
|
83
|
+
}, z.core.$strip>>>;
|
|
77
84
|
transport: z.ZodDefault<z.ZodEnum<{
|
|
78
85
|
http: "http";
|
|
79
86
|
sse: "sse";
|
|
@@ -89,6 +96,18 @@ declare const mcpServerSchema: z.ZodObject<{
|
|
|
89
96
|
* This is the "no MCP server to write" path: point Frankie at a spec, name the
|
|
90
97
|
* operations the agent may use, and it builds the tools at startup.
|
|
91
98
|
*/
|
|
99
|
+
declare const apiCapabilitySchema: z.ZodObject<{
|
|
100
|
+
operationId: z.ZodString;
|
|
101
|
+
method: z.ZodEnum<{
|
|
102
|
+
GET: "GET";
|
|
103
|
+
POST: "POST";
|
|
104
|
+
PUT: "PUT";
|
|
105
|
+
PATCH: "PATCH";
|
|
106
|
+
DELETE: "DELETE";
|
|
107
|
+
}>;
|
|
108
|
+
path: z.ZodString;
|
|
109
|
+
summary: z.ZodOptional<z.ZodString>;
|
|
110
|
+
}, z.core.$strip>;
|
|
92
111
|
declare const apiSourceSchema: z.ZodObject<{
|
|
93
112
|
name: z.ZodString;
|
|
94
113
|
spec: z.ZodString;
|
|
@@ -102,6 +121,21 @@ declare const apiSourceSchema: z.ZodObject<{
|
|
|
102
121
|
PATCH: "PATCH";
|
|
103
122
|
DELETE: "DELETE";
|
|
104
123
|
}>>>;
|
|
124
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
125
|
+
selectedTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
126
|
+
exposeAsMcp: z.ZodDefault<z.ZodBoolean>;
|
|
127
|
+
capabilities: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
128
|
+
operationId: z.ZodString;
|
|
129
|
+
method: z.ZodEnum<{
|
|
130
|
+
GET: "GET";
|
|
131
|
+
POST: "POST";
|
|
132
|
+
PUT: "PUT";
|
|
133
|
+
PATCH: "PATCH";
|
|
134
|
+
DELETE: "DELETE";
|
|
135
|
+
}>;
|
|
136
|
+
path: z.ZodString;
|
|
137
|
+
summary: z.ZodOptional<z.ZodString>;
|
|
138
|
+
}, z.core.$strip>>>;
|
|
105
139
|
headers: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
106
140
|
forwardUserTokenAs: z.ZodOptional<z.ZodString>;
|
|
107
141
|
forwardUserClaims: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
@@ -215,6 +249,12 @@ declare const frankieConfigSchema: z.ZodObject<{
|
|
|
215
249
|
}, z.core.$strip>;
|
|
216
250
|
mcpServers: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
217
251
|
name: z.ZodString;
|
|
252
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
253
|
+
selectedTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
254
|
+
discoveredTools: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
255
|
+
name: z.ZodString;
|
|
256
|
+
description: z.ZodOptional<z.ZodString>;
|
|
257
|
+
}, z.core.$strip>>>;
|
|
218
258
|
transport: z.ZodDefault<z.ZodEnum<{
|
|
219
259
|
http: "http";
|
|
220
260
|
sse: "sse";
|
|
@@ -237,6 +277,21 @@ declare const frankieConfigSchema: z.ZodObject<{
|
|
|
237
277
|
PATCH: "PATCH";
|
|
238
278
|
DELETE: "DELETE";
|
|
239
279
|
}>>>;
|
|
280
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
281
|
+
selectedTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
282
|
+
exposeAsMcp: z.ZodDefault<z.ZodBoolean>;
|
|
283
|
+
capabilities: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
284
|
+
operationId: z.ZodString;
|
|
285
|
+
method: z.ZodEnum<{
|
|
286
|
+
GET: "GET";
|
|
287
|
+
POST: "POST";
|
|
288
|
+
PUT: "PUT";
|
|
289
|
+
PATCH: "PATCH";
|
|
290
|
+
DELETE: "DELETE";
|
|
291
|
+
}>;
|
|
292
|
+
path: z.ZodString;
|
|
293
|
+
summary: z.ZodOptional<z.ZodString>;
|
|
294
|
+
}, z.core.$strip>>>;
|
|
240
295
|
headers: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
241
296
|
forwardUserTokenAs: z.ZodOptional<z.ZodString>;
|
|
242
297
|
forwardUserClaims: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
@@ -264,6 +319,7 @@ declare const frankieConfigSchema: z.ZodObject<{
|
|
|
264
319
|
provider: z.ZodOptional<z.ZodString>;
|
|
265
320
|
}, z.core.$strip>>>;
|
|
266
321
|
}, z.core.$strip>>;
|
|
322
|
+
workspaceAdmin: z.ZodDefault<z.ZodBoolean>;
|
|
267
323
|
}, z.core.$strip>>;
|
|
268
324
|
}, z.core.$strip>;
|
|
269
325
|
type ThinkingLevel = z.infer<typeof thinkingLevelSchema>;
|
|
@@ -272,6 +328,7 @@ type ModelChoice = z.infer<typeof modelChoiceSchema>;
|
|
|
272
328
|
type FeaturesConfig = z.infer<typeof featuresConfigSchema>;
|
|
273
329
|
type ModelConfig = z.infer<typeof modelConfigSchema>;
|
|
274
330
|
type McpServerConfig = z.infer<typeof mcpServerSchema>;
|
|
331
|
+
type ApiCapability = z.infer<typeof apiCapabilitySchema>;
|
|
275
332
|
type ApiSourceConfig = z.infer<typeof apiSourceSchema>;
|
|
276
333
|
type ToolPolicy = z.infer<typeof toolPolicySchema>;
|
|
277
334
|
type AgentConfig = z.infer<typeof agentConfigSchema>;
|
|
@@ -376,6 +433,8 @@ interface CreateSessionBody {
|
|
|
376
433
|
*/
|
|
377
434
|
userToken?: string;
|
|
378
435
|
context?: Record<string, string | number | boolean | null>;
|
|
436
|
+
/** Stable browser-generated id for a conversation that can resume on another device. */
|
|
437
|
+
conversationId?: string;
|
|
379
438
|
}
|
|
380
439
|
interface CreateSessionResponse {
|
|
381
440
|
/** Short-lived bearer token scoped to this one chat session. */
|
|
@@ -454,4 +513,29 @@ declare function evaluateToolPolicy(toolName: string, policy: ToolPolicy): ToolD
|
|
|
454
513
|
/** A short human label for a tool call, for the "Frankie is …" line in the UI. */
|
|
455
514
|
declare function describeTool(toolName: string, input: unknown): string;
|
|
456
515
|
|
|
457
|
-
|
|
516
|
+
/** One installation plan for GitHub and the local CLI. Never includes credentials. */
|
|
517
|
+
interface InstallProject {
|
|
518
|
+
files: Record<string, string>;
|
|
519
|
+
workspace: {
|
|
520
|
+
id: string;
|
|
521
|
+
name: string;
|
|
522
|
+
publishableKey: string;
|
|
523
|
+
allowAnonymous: boolean;
|
|
524
|
+
};
|
|
525
|
+
apiBaseUrl?: string;
|
|
526
|
+
}
|
|
527
|
+
interface InstallPlan {
|
|
528
|
+
files: Array<{
|
|
529
|
+
path: string;
|
|
530
|
+
content: string;
|
|
531
|
+
}>;
|
|
532
|
+
root: string;
|
|
533
|
+
chatPath: string;
|
|
534
|
+
specPath: string;
|
|
535
|
+
operationCount: number;
|
|
536
|
+
needsAuthSetup: boolean;
|
|
537
|
+
notes: string[];
|
|
538
|
+
}
|
|
539
|
+
declare function planHostedInstall(input: InstallProject): InstallPlan;
|
|
540
|
+
|
|
541
|
+
export { type AgentConfig, type ApiCapability, type ApiSourceConfig, type AuthConfig, type BrandingConfig, type ChatAttachment, type ChatRequestBody, type CreateSessionBody, type CreateSessionResponse, type FeaturesConfig, type FrankieConfig, type FrankieConfigInput, type FrankieErrorCode, type FrankieServerEvent, type InstallPlan, type InstallProject, type LimitsConfig, type McpServerConfig, type ModelChoice, type ModelConfig, type PublicFeatures, type PublicWidgetConfig, type StopReason, type ThinkingLevel, type ToolDecision, type ToolPolicy, type UsageSummary, type WidgetTheme, type WidgetThemeConfig, agentConfigSchema, apiCapabilitySchema, apiSourceSchema, authConfigSchema, brandingConfigSchema, defineFrankieConfig, describeTool, evaluateToolPolicy, featuresConfigSchema, frankieConfigSchema, limitsConfigSchema, mcpServerSchema, modelChoiceSchema, modelConfigSchema, parseFrankieConfig, planHostedInstall, thinkingLevelSchema, toolPolicySchema, widgetThemeSchema };
|
package/dist/index.d.ts
CHANGED
|
@@ -71,9 +71,16 @@ declare const featuresConfigSchema: z.ZodPrefault<z.ZodObject<{
|
|
|
71
71
|
provider: z.ZodOptional<z.ZodString>;
|
|
72
72
|
}, z.core.$strip>>>;
|
|
73
73
|
}, z.core.$strip>>;
|
|
74
|
+
workspaceAdmin: z.ZodDefault<z.ZodBoolean>;
|
|
74
75
|
}, z.core.$strip>>;
|
|
75
76
|
declare const mcpServerSchema: z.ZodObject<{
|
|
76
77
|
name: z.ZodString;
|
|
78
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
79
|
+
selectedTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
80
|
+
discoveredTools: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
81
|
+
name: z.ZodString;
|
|
82
|
+
description: z.ZodOptional<z.ZodString>;
|
|
83
|
+
}, z.core.$strip>>>;
|
|
77
84
|
transport: z.ZodDefault<z.ZodEnum<{
|
|
78
85
|
http: "http";
|
|
79
86
|
sse: "sse";
|
|
@@ -89,6 +96,18 @@ declare const mcpServerSchema: z.ZodObject<{
|
|
|
89
96
|
* This is the "no MCP server to write" path: point Frankie at a spec, name the
|
|
90
97
|
* operations the agent may use, and it builds the tools at startup.
|
|
91
98
|
*/
|
|
99
|
+
declare const apiCapabilitySchema: z.ZodObject<{
|
|
100
|
+
operationId: z.ZodString;
|
|
101
|
+
method: z.ZodEnum<{
|
|
102
|
+
GET: "GET";
|
|
103
|
+
POST: "POST";
|
|
104
|
+
PUT: "PUT";
|
|
105
|
+
PATCH: "PATCH";
|
|
106
|
+
DELETE: "DELETE";
|
|
107
|
+
}>;
|
|
108
|
+
path: z.ZodString;
|
|
109
|
+
summary: z.ZodOptional<z.ZodString>;
|
|
110
|
+
}, z.core.$strip>;
|
|
92
111
|
declare const apiSourceSchema: z.ZodObject<{
|
|
93
112
|
name: z.ZodString;
|
|
94
113
|
spec: z.ZodString;
|
|
@@ -102,6 +121,21 @@ declare const apiSourceSchema: z.ZodObject<{
|
|
|
102
121
|
PATCH: "PATCH";
|
|
103
122
|
DELETE: "DELETE";
|
|
104
123
|
}>>>;
|
|
124
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
125
|
+
selectedTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
126
|
+
exposeAsMcp: z.ZodDefault<z.ZodBoolean>;
|
|
127
|
+
capabilities: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
128
|
+
operationId: z.ZodString;
|
|
129
|
+
method: z.ZodEnum<{
|
|
130
|
+
GET: "GET";
|
|
131
|
+
POST: "POST";
|
|
132
|
+
PUT: "PUT";
|
|
133
|
+
PATCH: "PATCH";
|
|
134
|
+
DELETE: "DELETE";
|
|
135
|
+
}>;
|
|
136
|
+
path: z.ZodString;
|
|
137
|
+
summary: z.ZodOptional<z.ZodString>;
|
|
138
|
+
}, z.core.$strip>>>;
|
|
105
139
|
headers: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
106
140
|
forwardUserTokenAs: z.ZodOptional<z.ZodString>;
|
|
107
141
|
forwardUserClaims: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
@@ -215,6 +249,12 @@ declare const frankieConfigSchema: z.ZodObject<{
|
|
|
215
249
|
}, z.core.$strip>;
|
|
216
250
|
mcpServers: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
217
251
|
name: z.ZodString;
|
|
252
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
253
|
+
selectedTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
254
|
+
discoveredTools: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
255
|
+
name: z.ZodString;
|
|
256
|
+
description: z.ZodOptional<z.ZodString>;
|
|
257
|
+
}, z.core.$strip>>>;
|
|
218
258
|
transport: z.ZodDefault<z.ZodEnum<{
|
|
219
259
|
http: "http";
|
|
220
260
|
sse: "sse";
|
|
@@ -237,6 +277,21 @@ declare const frankieConfigSchema: z.ZodObject<{
|
|
|
237
277
|
PATCH: "PATCH";
|
|
238
278
|
DELETE: "DELETE";
|
|
239
279
|
}>>>;
|
|
280
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
281
|
+
selectedTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
282
|
+
exposeAsMcp: z.ZodDefault<z.ZodBoolean>;
|
|
283
|
+
capabilities: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
284
|
+
operationId: z.ZodString;
|
|
285
|
+
method: z.ZodEnum<{
|
|
286
|
+
GET: "GET";
|
|
287
|
+
POST: "POST";
|
|
288
|
+
PUT: "PUT";
|
|
289
|
+
PATCH: "PATCH";
|
|
290
|
+
DELETE: "DELETE";
|
|
291
|
+
}>;
|
|
292
|
+
path: z.ZodString;
|
|
293
|
+
summary: z.ZodOptional<z.ZodString>;
|
|
294
|
+
}, z.core.$strip>>>;
|
|
240
295
|
headers: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
241
296
|
forwardUserTokenAs: z.ZodOptional<z.ZodString>;
|
|
242
297
|
forwardUserClaims: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
@@ -264,6 +319,7 @@ declare const frankieConfigSchema: z.ZodObject<{
|
|
|
264
319
|
provider: z.ZodOptional<z.ZodString>;
|
|
265
320
|
}, z.core.$strip>>>;
|
|
266
321
|
}, z.core.$strip>>;
|
|
322
|
+
workspaceAdmin: z.ZodDefault<z.ZodBoolean>;
|
|
267
323
|
}, z.core.$strip>>;
|
|
268
324
|
}, z.core.$strip>;
|
|
269
325
|
type ThinkingLevel = z.infer<typeof thinkingLevelSchema>;
|
|
@@ -272,6 +328,7 @@ type ModelChoice = z.infer<typeof modelChoiceSchema>;
|
|
|
272
328
|
type FeaturesConfig = z.infer<typeof featuresConfigSchema>;
|
|
273
329
|
type ModelConfig = z.infer<typeof modelConfigSchema>;
|
|
274
330
|
type McpServerConfig = z.infer<typeof mcpServerSchema>;
|
|
331
|
+
type ApiCapability = z.infer<typeof apiCapabilitySchema>;
|
|
275
332
|
type ApiSourceConfig = z.infer<typeof apiSourceSchema>;
|
|
276
333
|
type ToolPolicy = z.infer<typeof toolPolicySchema>;
|
|
277
334
|
type AgentConfig = z.infer<typeof agentConfigSchema>;
|
|
@@ -376,6 +433,8 @@ interface CreateSessionBody {
|
|
|
376
433
|
*/
|
|
377
434
|
userToken?: string;
|
|
378
435
|
context?: Record<string, string | number | boolean | null>;
|
|
436
|
+
/** Stable browser-generated id for a conversation that can resume on another device. */
|
|
437
|
+
conversationId?: string;
|
|
379
438
|
}
|
|
380
439
|
interface CreateSessionResponse {
|
|
381
440
|
/** Short-lived bearer token scoped to this one chat session. */
|
|
@@ -454,4 +513,29 @@ declare function evaluateToolPolicy(toolName: string, policy: ToolPolicy): ToolD
|
|
|
454
513
|
/** A short human label for a tool call, for the "Frankie is …" line in the UI. */
|
|
455
514
|
declare function describeTool(toolName: string, input: unknown): string;
|
|
456
515
|
|
|
457
|
-
|
|
516
|
+
/** One installation plan for GitHub and the local CLI. Never includes credentials. */
|
|
517
|
+
interface InstallProject {
|
|
518
|
+
files: Record<string, string>;
|
|
519
|
+
workspace: {
|
|
520
|
+
id: string;
|
|
521
|
+
name: string;
|
|
522
|
+
publishableKey: string;
|
|
523
|
+
allowAnonymous: boolean;
|
|
524
|
+
};
|
|
525
|
+
apiBaseUrl?: string;
|
|
526
|
+
}
|
|
527
|
+
interface InstallPlan {
|
|
528
|
+
files: Array<{
|
|
529
|
+
path: string;
|
|
530
|
+
content: string;
|
|
531
|
+
}>;
|
|
532
|
+
root: string;
|
|
533
|
+
chatPath: string;
|
|
534
|
+
specPath: string;
|
|
535
|
+
operationCount: number;
|
|
536
|
+
needsAuthSetup: boolean;
|
|
537
|
+
notes: string[];
|
|
538
|
+
}
|
|
539
|
+
declare function planHostedInstall(input: InstallProject): InstallPlan;
|
|
540
|
+
|
|
541
|
+
export { type AgentConfig, type ApiCapability, type ApiSourceConfig, type AuthConfig, type BrandingConfig, type ChatAttachment, type ChatRequestBody, type CreateSessionBody, type CreateSessionResponse, type FeaturesConfig, type FrankieConfig, type FrankieConfigInput, type FrankieErrorCode, type FrankieServerEvent, type InstallPlan, type InstallProject, type LimitsConfig, type McpServerConfig, type ModelChoice, type ModelConfig, type PublicFeatures, type PublicWidgetConfig, type StopReason, type ThinkingLevel, type ToolDecision, type ToolPolicy, type UsageSummary, type WidgetTheme, type WidgetThemeConfig, agentConfigSchema, apiCapabilitySchema, apiSourceSchema, authConfigSchema, brandingConfigSchema, defineFrankieConfig, describeTool, evaluateToolPolicy, featuresConfigSchema, frankieConfigSchema, limitsConfigSchema, mcpServerSchema, modelChoiceSchema, modelConfigSchema, parseFrankieConfig, planHostedInstall, thinkingLevelSchema, toolPolicySchema, widgetThemeSchema };
|
package/dist/index.js
CHANGED
|
@@ -60,10 +60,20 @@ var featuresConfigSchema = z.object({
|
|
|
60
60
|
enabled: z.boolean().default(false),
|
|
61
61
|
/** The models offered to end users. The workspace default is always allowed. */
|
|
62
62
|
models: z.array(modelChoiceSchema).default([])
|
|
63
|
-
}).prefault({})
|
|
63
|
+
}).prefault({}),
|
|
64
|
+
/**
|
|
65
|
+
* Platform onboarding: give the agent built-in tools to create and list the
|
|
66
|
+
* signed-in user's own Frankie workspaces. Only for a workspace that *is* the
|
|
67
|
+
* Frankie onboarding assistant — a normal customer never turns this on.
|
|
68
|
+
*/
|
|
69
|
+
workspaceAdmin: z.boolean().default(false)
|
|
64
70
|
}).prefault({});
|
|
65
71
|
var mcpServerSchema = z.object({
|
|
66
72
|
name: z.string().min(1).regex(/^[a-z0-9][a-z0-9_-]*$/i, "MCP server names must be alphanumeric with - or _"),
|
|
73
|
+
enabled: z.boolean().default(true),
|
|
74
|
+
/** Explicit tool selection. Omitted keeps legacy all-tools behavior; [] disables all. */
|
|
75
|
+
selectedTools: z.array(z.string().min(1)).optional(),
|
|
76
|
+
discoveredTools: z.array(z.object({ name: z.string(), description: z.string().optional() })).default([]),
|
|
67
77
|
/** `http` covers streamable HTTP; `sse` is the legacy transport. */
|
|
68
78
|
transport: z.enum(["http", "sse"]).default("http"),
|
|
69
79
|
url: z.string().url(),
|
|
@@ -78,6 +88,12 @@ var mcpServerSchema = z.object({
|
|
|
78
88
|
/** Extra claims from the user token to forward as `x-frankie-<claim>`. */
|
|
79
89
|
forwardUserClaims: z.array(z.string()).default([])
|
|
80
90
|
});
|
|
91
|
+
var apiCapabilitySchema = z.object({
|
|
92
|
+
operationId: z.string().min(1),
|
|
93
|
+
method: z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
|
|
94
|
+
path: z.string().min(1),
|
|
95
|
+
summary: z.string().optional()
|
|
96
|
+
});
|
|
81
97
|
var apiSourceSchema = z.object({
|
|
82
98
|
name: z.string().min(1).regex(/^[a-z0-9][a-z0-9_-]*$/i, "API source names must be alphanumeric with - or _"),
|
|
83
99
|
/** URL or local path to an OpenAPI 3.x document (JSON or YAML). */
|
|
@@ -99,6 +115,13 @@ var apiSourceSchema = z.object({
|
|
|
99
115
|
* deliberately, per API.
|
|
100
116
|
*/
|
|
101
117
|
methods: z.array(z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"])).default(["GET"]),
|
|
118
|
+
enabled: z.boolean().default(true),
|
|
119
|
+
/** Explicit operation selection, independent of the discovered inventory. */
|
|
120
|
+
selectedTools: z.array(z.string().min(1)).optional(),
|
|
121
|
+
/** Whether these approved API tools are also served by the workspace MCP. */
|
|
122
|
+
exposeAsMcp: z.boolean().default(true),
|
|
123
|
+
/** A read-only inventory derived from the source for the dashboard. */
|
|
124
|
+
capabilities: z.array(apiCapabilitySchema).default([]),
|
|
102
125
|
/** Static headers, e.g. a service token. */
|
|
103
126
|
headers: z.record(z.string(), z.string()).default({}),
|
|
104
127
|
/**
|
|
@@ -236,8 +259,196 @@ function humanise(name) {
|
|
|
236
259
|
function truncate(value, max) {
|
|
237
260
|
return value.length <= max ? value : `${value.slice(0, max - 1)}\u2026`;
|
|
238
261
|
}
|
|
262
|
+
|
|
263
|
+
// src/install.ts
|
|
264
|
+
function planHostedInstall(input) {
|
|
265
|
+
const { files, workspace } = input;
|
|
266
|
+
const layouts = Object.keys(files).filter((p) => /(?:^|\/)(?:src\/)?app(?:\/\([^/]+\))?\/layout\.tsx$/.test(p));
|
|
267
|
+
const rootLayoutPath = layouts.find((p) => !/(?:^|\/)app\/\([^/]+\)\/layout\.tsx$/.test(p)) ?? layouts[0];
|
|
268
|
+
if (!rootLayoutPath) throw new Error("Automatic installation supports Next.js App Router. No app/layout.tsx was found.");
|
|
269
|
+
const layoutPath = layouts.find((p) => /(?:^|\/)app\/\((?:dashboard|authenticated|protected|app)\)\/layout\.tsx$/.test(p)) ?? rootLayoutPath;
|
|
270
|
+
const appRoot = rootLayoutPath.slice(0, -"layout.tsx".length);
|
|
271
|
+
const integrationAppRoot = layoutPath.slice(0, -"layout.tsx".length);
|
|
272
|
+
const root = appRoot.replace(/(?:src\/)?app\/$/, "");
|
|
273
|
+
const pkgPath = root + "package.json";
|
|
274
|
+
const pkg = JSON.parse(files[pkgPath] ?? "{}");
|
|
275
|
+
if (!pkg.dependencies?.next) throw new Error("The selected app must have Next.js in dependencies.");
|
|
276
|
+
const out = /* @__PURE__ */ new Map();
|
|
277
|
+
const notes = [];
|
|
278
|
+
const existingMount = files[layoutPath].includes("FrankieWidgetMount");
|
|
279
|
+
const sourceRoot = appRoot.endsWith("src/app/") ? root + "src/" : root;
|
|
280
|
+
const firebase = Boolean(files[sourceRoot + "lib/firebase-admin.ts"]?.includes("getAdminAuth") && files[sourceRoot + "lib/session.ts"]?.includes("SESSION_COOKIE"));
|
|
281
|
+
const authFile = files[sourceRoot + "auth.ts"];
|
|
282
|
+
const authJs = Boolean(authFile?.includes("NextAuth") && /export\s+(?:const|let)\s*\{[^}]*\bauth\b/.test(authFile ?? ""));
|
|
283
|
+
const tokenPath = appRoot + "api/frankie/token/route.ts";
|
|
284
|
+
const needsAuthSetup = !workspace.allowAnonymous && !files[tokenPath] && !firebase && !authJs;
|
|
285
|
+
if (needsAuthSetup) notes.push("Connect getCurrentUser in the generated token endpoint to your server session. It rejects requests until configured.");
|
|
286
|
+
pkg.dependencies = { ...pkg.dependencies, "@frankieai/react": pkg.dependencies["@frankieai/react"] ?? "^0.1.5", ...!workspace.allowAnonymous ? { jose: pkg.dependencies.jose ?? "^6.0.11" } : {} };
|
|
287
|
+
out.set(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
288
|
+
const authProp = workspace.allowAnonymous ? "" : " getUserToken={getUserToken}";
|
|
289
|
+
const shared = `import { FrankieProvider, FrankieWidget, FrankieChatPage } from "@frankieai/react";
|
|
290
|
+
import "@frankieai/react/styles.css";
|
|
291
|
+
`;
|
|
292
|
+
const getToken = workspace.allowAnonymous ? "" : `
|
|
293
|
+
async function getUserToken() {
|
|
294
|
+
const response = await fetch("/api/frankie/token", { method: "POST", credentials: "same-origin" });
|
|
295
|
+
if (!response.ok) return undefined;
|
|
296
|
+
return ((await response.json()) as { token?: string }).token;
|
|
297
|
+
}
|
|
298
|
+
`;
|
|
299
|
+
let priorChat;
|
|
300
|
+
try {
|
|
301
|
+
const previous = JSON.parse(files[root + "frankie.install.json"] ?? "{}");
|
|
302
|
+
if (previous.workspaceId === workspace.id && ["/chat", "/frankie/chat"].includes(previous.chatPath)) priorChat = previous.chatPath;
|
|
303
|
+
} catch {
|
|
304
|
+
}
|
|
305
|
+
const chatPath = priorChat ?? (files[integrationAppRoot + "chat/page.tsx"] ? "/frankie/chat" : "/chat");
|
|
306
|
+
if (!priorChat && files[integrationAppRoot + chatPath.slice(1) + "/page.tsx"]) throw new Error("Both /chat and /frankie/chat already exist. Choose a chat route manually.");
|
|
307
|
+
out.set(root + "frankie.install.json", JSON.stringify({ workspaceId: workspace.id, chatPath }, null, 2) + "\n");
|
|
308
|
+
if (chatPath !== "/chat") notes.push("Existing /chat preserved. Frankie chat is available at /frankie/chat.");
|
|
309
|
+
const mount = `"use client";
|
|
310
|
+
import { usePathname } from "next/navigation";
|
|
311
|
+
${shared}${getToken}
|
|
312
|
+
export function FrankieWidgetMount() {
|
|
313
|
+
const pathname = usePathname();
|
|
314
|
+
const key = process.env.NEXT_PUBLIC_FRANKIE_KEY;
|
|
315
|
+
if (!key || pathname === ${JSON.stringify(chatPath)}) return null;
|
|
316
|
+
return <FrankieProvider publishableKey={key}${authProp}><FrankieWidget /></FrankieProvider>;
|
|
317
|
+
}
|
|
318
|
+
export function WorkspaceChatPage() {
|
|
319
|
+
const key = process.env.NEXT_PUBLIC_FRANKIE_KEY;
|
|
320
|
+
if (!key) return <p>Chat is not configured. Set NEXT_PUBLIC_FRANKIE_KEY in your deployment.</p>;
|
|
321
|
+
return <FrankieChatPage publishableKey={key}${authProp} />;
|
|
322
|
+
}
|
|
323
|
+
`;
|
|
324
|
+
const moduleName = existingMount ? "FrankieSetupChat" : "FrankieWidgetMount";
|
|
325
|
+
out.set(integrationAppRoot + "frankie/" + moduleName + ".tsx", mount);
|
|
326
|
+
if (!existingMount) {
|
|
327
|
+
const layout = files[layoutPath];
|
|
328
|
+
const importLine = `import { FrankieWidgetMount } from "./frankie/FrankieWidgetMount";
|
|
329
|
+
`;
|
|
330
|
+
const withImport = /^["']use client["'];/.test(layout) ? layout.replace(/^(['"]use client['"];)/, "$1\n" + importLine) : importLine + layout;
|
|
331
|
+
if (withImport.includes("</body>")) {
|
|
332
|
+
out.set(layoutPath, withImport.replace("</body>", " <FrankieWidgetMount />\n</body>"));
|
|
333
|
+
} else if (withImport.includes("</main>")) {
|
|
334
|
+
out.set(layoutPath, withImport.replace("</main>", " <FrankieWidgetMount />\n</main>"));
|
|
335
|
+
} else if (withImport.includes("{children}")) {
|
|
336
|
+
out.set(layoutPath, withImport.replace("{children}", "{children}<FrankieWidgetMount />"));
|
|
337
|
+
} else {
|
|
338
|
+
throw new Error("The selected layout has no safe widget mount point. Add FrankieWidgetMount manually.");
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
const pageImport = chatPath === "/chat" ? "../frankie/" : "../../frankie/";
|
|
342
|
+
out.set(integrationAppRoot + chatPath.slice(1) + "/page.tsx", `import { WorkspaceChatPage } from "${pageImport}${moduleName}";
|
|
343
|
+
export default WorkspaceChatPage;
|
|
344
|
+
`);
|
|
345
|
+
if (!workspace.allowAnonymous && !files[tokenPath]) {
|
|
346
|
+
const lookup = firebase ? `import { cookies } from "next/headers";
|
|
347
|
+
import { getAdminAuth } from "@/lib/firebase-admin";
|
|
348
|
+
import { SESSION_COOKIE } from "@/lib/session";
|
|
349
|
+
async function getCurrentUser() {
|
|
350
|
+
const cookie = (await cookies()).get(SESSION_COOKIE)?.value;
|
|
351
|
+
if (!cookie) return null;
|
|
352
|
+
const user = await getAdminAuth().verifySessionCookie(cookie, true);
|
|
353
|
+
return { id: user.uid, name: user.name, email: user.email };
|
|
354
|
+
}
|
|
355
|
+
` : authJs ? `import { auth } from "@/auth";
|
|
356
|
+
async function getCurrentUser() {
|
|
357
|
+
const session = await auth();
|
|
358
|
+
const user = session?.user as { id?: string; name?: string | null; email?: string | null } | undefined;
|
|
359
|
+
return user?.id ? user : null;
|
|
360
|
+
}
|
|
361
|
+
` : `// Replace with your app's verified server session lookup.
|
|
362
|
+
async function getCurrentUser(): Promise<{ id: string; name?: string; email?: string } | null> { return null; }
|
|
363
|
+
`;
|
|
364
|
+
out.set(tokenPath, `import { SignJWT } from "jose";
|
|
365
|
+
import { NextResponse } from "next/server";
|
|
366
|
+
${lookup}
|
|
367
|
+
export async function POST() {
|
|
368
|
+
const secret = process.env.FRANKIE_JWT_SECRET;
|
|
369
|
+
if (!secret) return NextResponse.json({ error: "Chat secret not configured" }, { status: 503 });
|
|
370
|
+
try {
|
|
371
|
+
const user = await getCurrentUser();
|
|
372
|
+
if (!user?.id) return NextResponse.json({ error: "Sign in required" }, { status: 401 });
|
|
373
|
+
const token = await new SignJWT({ name: user.name, email: user.email })
|
|
374
|
+
.setProtectedHeader({ alg: "HS256" }).setSubject(user.id).setIssuedAt()
|
|
375
|
+
.setExpirationTime("5m").sign(new TextEncoder().encode(secret));
|
|
376
|
+
return NextResponse.json({ token }, { headers: { "Cache-Control": "no-store" } });
|
|
377
|
+
} catch { return NextResponse.json({ error: "Sign in required" }, { status: 401 }); }
|
|
378
|
+
}
|
|
379
|
+
`);
|
|
380
|
+
}
|
|
381
|
+
let doc;
|
|
382
|
+
for (const [path, content] of Object.entries(files)) {
|
|
383
|
+
if (!/(?:openapi|swagger)[^/]*\.json$/i.test(path)) continue;
|
|
384
|
+
try {
|
|
385
|
+
const parsed = JSON.parse(content);
|
|
386
|
+
if (parsed.openapi && parsed.paths) {
|
|
387
|
+
doc = parsed;
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
} catch {
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const paths = {};
|
|
394
|
+
if (!doc) for (const [file, content] of Object.entries(files)) {
|
|
395
|
+
if (/\.(test|spec)\.[tj]sx?$/.test(file)) continue;
|
|
396
|
+
const nextRoute = file.match(/(?:^|\/)app(\/.*)\/route\.[tj]s$/);
|
|
397
|
+
if (nextRoute) {
|
|
398
|
+
const path = nextRoute[1].replace(/\/\([^/]+\)/g, "").replace(/\[([^\]]+)\]/g, "{$1}");
|
|
399
|
+
if (path.includes("...") || /\/(?:auth|frankie)(?:\/|$)/.test(path)) continue;
|
|
400
|
+
for (const method of ["GET"]) {
|
|
401
|
+
if (new RegExp("export\\s+(?:(?:async\\s+)?function\\s+" + method + "\\b|(?:const|let)\\s+" + method + "\\s*=)").test(content))
|
|
402
|
+
addInstallOperation(paths, method, path, file);
|
|
403
|
+
}
|
|
404
|
+
} else if (/\.[tj]s$/.test(file) && /(?:routes?|api)\//.test(file)) {
|
|
405
|
+
for (const match of content.matchAll(/\b(?:app|router|fastify)\.(get|post|put|patch|delete)(?:\s*<[^;]{1,1600}?>)?\s*\(\s*["']([^"']+)["']/g))
|
|
406
|
+
addInstallOperation(paths, match[1].toUpperCase(), match[2].replace(/:([A-Za-z0-9_]+)/g, "{$1}"), file);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
doc ??= { openapi: "3.0.3", info: { title: workspace.name + " API", version: "1.0.0", description: "Read-only generated route inventory. Review schemas, authorization, and the deployed origin before enabling tools." }, paths };
|
|
410
|
+
const specPath = root + "public/frankie.openapi.json";
|
|
411
|
+
const operationCount = Object.values(doc.paths).reduce((n, item) => n + Object.keys(item).filter((m) => /^(get|post|put|patch|delete)$/.test(m)).length, 0);
|
|
412
|
+
out.set(specPath, JSON.stringify(doc, null, 2) + "\n");
|
|
413
|
+
if (!operationCount) notes.push("No supported API routes detected. The OpenAPI draft has no operations; add the real schema before connecting tools.");
|
|
414
|
+
notes.push("Generated OpenAPI is read-only (GET only). Add write operations only to a reviewed, user-authorized schema.");
|
|
415
|
+
notes.push("Verify the public spec URL is reachable without dashboard authentication before connecting it.");
|
|
416
|
+
notes.push("The token endpoint issues a signed user JWT, but your product API must validate it and enforce user/resource permissions before exposing tools.");
|
|
417
|
+
const envPath = root + ".env.example";
|
|
418
|
+
const env = (files[envPath] ?? "").replace(/^NEXT_PUBLIC_FRANKIE_KEY=.*\n?/gm, "").replace(/^FRANKIE_JWT_SECRET=.*\n?/gm, "").trimEnd();
|
|
419
|
+
out.set(envPath, env + "\nNEXT_PUBLIC_FRANKIE_KEY=" + workspace.publishableKey + "\n" + (workspace.allowAnonymous ? "" : "# Server only: use the secret from this workspace. Never commit its value.\nFRANKIE_JWT_SECRET=\n"));
|
|
420
|
+
out.set(root + "FRANKIE_SETUP.md", `# Frankie setup
|
|
421
|
+
|
|
422
|
+
Workspace: ${workspace.name}
|
|
423
|
+
|
|
424
|
+
1. Add NEXT_PUBLIC_FRANKIE_KEY from .env.example to the frontend deployment.
|
|
425
|
+
${workspace.allowAnonymous ? "" : "2. Add FRANKIE_JWT_SECRET from this workspace to the server hosting /api/frankie/token. Never commit the value.\n"}3. Run your package manager and commit the updated lockfile (for pnpm: pnpm install --lockfile-only). The remote GitHub installer cannot safely synthesize a lockfile.
|
|
426
|
+
4. Confirm https://YOUR_APP/frankie.openapi.json is public. If your Next.js middleware protects all paths, explicitly bypass /frankie.openapi.json before connecting it.
|
|
427
|
+
5. Open ${chatPath}. It is created inside the detected authenticated dashboard group when one exists; add its link to the host sidebar during review.
|
|
428
|
+
6. Connect https://YOUR_APP/frankie.openapi.json in Frankie \u2192 Tools & MCP. The generated document contains only read-only GET operations.
|
|
429
|
+
7. Forwarding sends the Frankie-signed JWT. Your API must validate it and enforce user/resource permissions. It is not automatically interchangeable with a Firebase or OAuth access token.
|
|
430
|
+
8. Add NEXT_PUBLIC_FRANKIE_KEY to every frontend deployment environment (Preview and Production). ${workspace.allowAnonymous ? "" : "Add FRANKIE_JWT_SECRET only to the server environment running /api/frankie/token; never expose it to the browser.\n"}9. Review conversations in Workspace \u2192 Chat sessions.
|
|
431
|
+
|
|
432
|
+
## Remaining setup
|
|
433
|
+
|
|
434
|
+
${notes.map((n) => "- " + n).join("\n")}
|
|
435
|
+
`);
|
|
436
|
+
return { files: [...out].map(([path, content]) => ({ path, content })), root, chatPath, specPath, operationCount, needsAuthSetup, notes };
|
|
437
|
+
}
|
|
438
|
+
function addInstallOperation(paths, method, path, file) {
|
|
439
|
+
if (path.includes("*") || !path.startsWith("/")) return;
|
|
440
|
+
(paths[path] ??= {})[method.toLowerCase()] = {
|
|
441
|
+
operationId: (method.toLowerCase() + "_" + path).replace(/[^a-zA-Z0-9_]+/g, "_"),
|
|
442
|
+
summary: method + " " + path,
|
|
443
|
+
description: "Detected in " + file + ". Review schemas before enabling.",
|
|
444
|
+
parameters: [...path.matchAll(/\{([^}]+)\}/g)].map((m) => ({ name: m[1], in: "path", required: true, schema: { type: "string" } })),
|
|
445
|
+
responses: { "200": { description: "Successful response" } },
|
|
446
|
+
"x-frankie-review-required": true
|
|
447
|
+
};
|
|
448
|
+
}
|
|
239
449
|
export {
|
|
240
450
|
agentConfigSchema,
|
|
451
|
+
apiCapabilitySchema,
|
|
241
452
|
apiSourceSchema,
|
|
242
453
|
authConfigSchema,
|
|
243
454
|
brandingConfigSchema,
|
|
@@ -251,6 +462,7 @@ export {
|
|
|
251
462
|
modelChoiceSchema,
|
|
252
463
|
modelConfigSchema,
|
|
253
464
|
parseFrankieConfig,
|
|
465
|
+
planHostedInstall,
|
|
254
466
|
thinkingLevelSchema,
|
|
255
467
|
toolPolicySchema,
|
|
256
468
|
widgetThemeSchema
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config.ts","../src/tools.ts"],"sourcesContent":["import { z } from \"zod\";\n\n/**\n * Tenant configuration. One config == one embedded agent. Everything a\n * customer needs to change to make Frankie theirs lives here, so the code\n * itself stays generic.\n */\n\nexport const widgetThemeSchema = z\n .object({\n primary: z.string().optional(),\n primaryForeground: z.string().optional(),\n background: z.string().optional(),\n foreground: z.string().optional(),\n muted: z.string().optional(),\n border: z.string().optional(),\n radius: z.string().optional(),\n fontFamily: z.string().optional(),\n mode: z.enum([\"light\", \"dark\", \"system\"]).default(\"system\"),\n })\n .prefault({});\n\nexport const thinkingLevelSchema = z.enum([\"minimal\", \"low\", \"medium\", \"high\", \"xhigh\", \"max\"]);\n\nexport const modelConfigSchema = z.object({\n /**\n * Any provider the model layer knows about. `fireworks` is the default;\n * `anthropic`, `openai`, `google`, `groq`, `together`, `openrouter` and\n * ~30 others resolve too, which is what makes the model a config choice.\n */\n provider: z.string().min(1).default(\"fireworks\"),\n /**\n * Model id within that provider. The default is Kimi K2.7 on Fireworks,\n * which serves a native Anthropic-Messages endpoint and a 262K window.\n */\n model: z.string().default(\"accounts/fireworks/models/kimi-k2p7-code\"),\n /** Env var holding the provider key. Never put the key in config itself. */\n apiKeyEnv: z.string().default(\"FIREWORKS_API_KEY\"),\n /** Override the provider's base URL (self-hosted gateways, tests). */\n baseUrl: z.string().url().optional(),\n maxTokens: z.number().int().positive().max(200_000).default(8192),\n temperature: z.number().min(0).max(2).default(0.6),\n /**\n * Reasoning depth. Lowest by default: an onboarding widget is latency-\n * sensitive and its questions rarely need deliberation.\n */\n thinkingLevel: thinkingLevelSchema.default(\"minimal\"),\n /** Forward the model's reasoning stream to the client as thinking deltas. */\n streamReasoning: z.boolean().default(false),\n});\n\n/** One selectable model in the (optional) end-user model picker. */\nexport const modelChoiceSchema = z.object({\n /** Provider model id, e.g. \"accounts/fireworks/models/kimi-k2p7-code\". */\n id: z.string().min(1),\n /** Shown in the picker, e.g. \"Fast\" or \"Smart\". */\n label: z.string().min(1),\n /** Defaults to the workspace's default provider when omitted. */\n provider: z.string().optional(),\n});\n\n/**\n * Optional, opt-in chat capabilities the SDK consumer turns on per workspace.\n * Everything here is OFF by default: an embedded agent only gets a wider surface\n * when the customer deliberately enables it.\n */\nexport const featuresConfigSchema = z\n .object({\n /** Let end users attach files. Images go to a vision model; docs are read as text. */\n attachments: z\n .object({\n enabled: z.boolean().default(false),\n /** Accepted MIME patterns, e.g. \"image/*\", \"text/*\", \"application/json\". */\n accept: z.array(z.string()).default([\"image/*\", \"text/*\", \"application/json\"]),\n maxMb: z.number().positive().max(25).default(10),\n maxCount: z.number().int().positive().max(10).default(4),\n })\n .prefault({}),\n /** Let end users choose the model, from a curated list you control. */\n modelPicker: z\n .object({\n enabled: z.boolean().default(false),\n /** The models offered to end users. The workspace default is always allowed. */\n models: z.array(modelChoiceSchema).default([]),\n })\n .prefault({}),\n })\n .prefault({});\n\nexport const mcpServerSchema = z.object({\n name: z\n .string()\n .min(1)\n .regex(/^[a-z0-9][a-z0-9_-]*$/i, \"MCP server names must be alphanumeric with - or _\"),\n /** `http` covers streamable HTTP; `sse` is the legacy transport. */\n transport: z.enum([\"http\", \"sse\"]).default(\"http\"),\n url: z.string().url(),\n /** Static headers, e.g. a service token for the company's own MCP server. */\n headers: z.record(z.string(), z.string()).default({}),\n /**\n * When set, the verified end-user JWT is forwarded to the MCP server in this\n * header so the company's tools can act as that user. Use with care: only\n * enable it for MCP servers you control.\n */\n forwardUserTokenAs: z.string().optional(),\n /** Extra claims from the user token to forward as `x-frankie-<claim>`. */\n forwardUserClaims: z.array(z.string()).default([]),\n});\n\n/**\n * An HTTP API described by an OpenAPI document, turned into tools directly.\n *\n * This is the \"no MCP server to write\" path: point Frankie at a spec, name the\n * operations the agent may use, and it builds the tools at startup.\n */\nexport const apiSourceSchema = z.object({\n name: z\n .string()\n .min(1)\n .regex(/^[a-z0-9][a-z0-9_-]*$/i, \"API source names must be alphanumeric with - or _\"),\n /** URL or local path to an OpenAPI 3.x document (JSON or YAML). */\n spec: z.string().min(1),\n /** Overrides the spec's own `servers[0].url`. Required if the spec has none. */\n baseUrl: z.string().url().optional(),\n /**\n * Operation ids the agent may call. Empty means \"every operation that passes\n * the other filters\" — fine for a small internal API, risky for a large one.\n */\n operations: z.array(z.string()).default([]),\n /**\n * Path globs to include, e.g. `/projects/**`. Empty means all paths.\n */\n includePaths: z.array(z.string()).default([]),\n /**\n * HTTP methods the agent may use. Read-only by default: an onboarding agent\n * wired to a whole API should not be able to DELETE on its first day. Widen\n * deliberately, per API.\n */\n methods: z.array(z.enum([\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"])).default([\"GET\"]),\n /** Static headers, e.g. a service token. */\n headers: z.record(z.string(), z.string()).default({}),\n /**\n * When set, the verified end-user JWT is forwarded in this header, so your\n * API authorises the call exactly as it would any other request from them.\n */\n forwardUserTokenAs: z.string().optional(),\n /** Extra claims from the user token to forward as `x-frankie-<claim>`. */\n forwardUserClaims: z.array(z.string()).default([]),\n /** Per-call timeout. */\n timeoutMs: z.number().int().positive().default(30_000),\n /** Response bodies longer than this are truncated before the model sees them. */\n maxResponseChars: z.number().int().positive().default(8_000),\n});\n\nexport const toolPolicySchema = z.object({\n /**\n * Tool names the agent may call without asking. MCP tools are named\n * `mcp__<server>__<tool>` and generated API tools `api__<name>__<operation>`;\n * naming the prefix alone (`mcp__acme`, `api__acme`) allows all of them.\n */\n allow: z.array(z.string()).default([]),\n /** Explicit denials; evaluated before `allow`. */\n deny: z.array(z.string()).default([]),\n /**\n * Deny anything not in `allow`. On by default — an onboarding agent living\n * on a public marketing page should not have an open tool surface.\n */\n denyByDefault: z.boolean().default(true),\n});\n\nexport const agentConfigSchema = z.object({\n /** Prepended to the runtime's own operating instructions. */\n systemPrompt: z.string().default(\"\"),\n /** First message the widget shows before the user types anything. */\n greeting: z.string().default(\"Hi! I can walk you through getting set up. What are you trying to do?\"),\n suggestedPrompts: z.array(z.string()).max(6).default([]),\n maxTurns: z.number().int().positive().max(50).default(12),\n /** Hard stop on estimated spend per session. */\n maxBudgetUsd: z.number().positive().optional(),\n tools: toolPolicySchema.prefault({}),\n /** Show tool activity in the transcript. */\n showToolActivity: z.boolean().default(true),\n});\n\nexport const authConfigSchema = z.object({\n /**\n * Safe to ship in client bundles. Identifies the tenant; it authorises\n * nothing on its own — the origin allowlist and the user token do that.\n */\n publishableKey: z.string().min(8),\n /** Exact origins allowed to open sessions, e.g. `https://app.acme.com`. */\n allowedOrigins: z.array(z.string()).default([]),\n /** Let visitors chat without a host-signed token (marketing pages). */\n allowAnonymous: z.boolean().default(false),\n /** Env var holding the HS256 secret the host signs user tokens with. */\n jwtSecretEnv: z.string().default(\"FRANKIE_JWT_SECRET\"),\n /** Verify against a JWKS endpoint instead of a shared secret (RS/ES256). */\n jwksUrl: z.string().url().optional(),\n jwtIssuer: z.string().optional(),\n jwtAudience: z.string().optional(),\n /** How long an issued session token is good for. */\n sessionTtlSeconds: z.number().int().positive().default(60 * 60),\n /** Env var holding the secret used to sign Frankie's own session tokens. */\n sessionSecretEnv: z.string().default(\"FRANKIE_SESSION_SECRET\"),\n});\n\nexport const limitsConfigSchema = z.object({\n /** Messages per session per window. */\n messagesPerWindow: z.number().int().positive().default(30),\n windowSeconds: z.number().int().positive().default(15 * 60),\n /** Sessions a single IP may open per window. */\n sessionsPerIpPerWindow: z.number().int().positive().default(20),\n /** Longest a single turn may run before it is aborted. */\n turnTimeoutMs: z.number().int().positive().default(120_000),\n});\n\nexport const brandingConfigSchema = z.object({\n name: z.string().default(\"Assistant\"),\n tagline: z.string().optional(),\n avatarUrl: z.string().url().optional(),\n launcherLabel: z.string().default(\"Need a hand?\"),\n});\n\nexport const frankieConfigSchema = z.object({\n tenantId: z.string().min(1),\n branding: brandingConfigSchema.prefault({}),\n theme: widgetThemeSchema,\n model: modelConfigSchema.prefault({}),\n agent: agentConfigSchema.prefault({}),\n auth: authConfigSchema,\n mcpServers: z.array(mcpServerSchema).default([]),\n /** APIs turned into tools from their OpenAPI document. */\n apis: z.array(apiSourceSchema).default([]),\n limits: limitsConfigSchema.prefault({}),\n /** Opt-in end-user chat capabilities (attachments, model picker). */\n features: featuresConfigSchema,\n});\n\nexport type ThinkingLevel = z.infer<typeof thinkingLevelSchema>;\nexport type WidgetThemeConfig = z.infer<typeof widgetThemeSchema>;\nexport type ModelChoice = z.infer<typeof modelChoiceSchema>;\nexport type FeaturesConfig = z.infer<typeof featuresConfigSchema>;\nexport type ModelConfig = z.infer<typeof modelConfigSchema>;\nexport type McpServerConfig = z.infer<typeof mcpServerSchema>;\nexport type ApiSourceConfig = z.infer<typeof apiSourceSchema>;\nexport type ToolPolicy = z.infer<typeof toolPolicySchema>;\nexport type AgentConfig = z.infer<typeof agentConfigSchema>;\nexport type AuthConfig = z.infer<typeof authConfigSchema>;\nexport type LimitsConfig = z.infer<typeof limitsConfigSchema>;\nexport type BrandingConfig = z.infer<typeof brandingConfigSchema>;\nexport type FrankieConfig = z.infer<typeof frankieConfigSchema>;\nexport type FrankieConfigInput = z.input<typeof frankieConfigSchema>;\n\nexport function parseFrankieConfig(input: unknown): FrankieConfig {\n return frankieConfigSchema.parse(input);\n}\n\n/** Helper for `frankie.config.ts` files — gives editors full completion. */\nexport function defineFrankieConfig(config: FrankieConfigInput): FrankieConfigInput {\n return config;\n}\n","import type { ToolPolicy } from \"./config.js\";\n\nexport interface ToolDecision {\n allowed: boolean;\n reason: string;\n}\n\n/**\n * Decide whether the agent may run a tool.\n *\n * Patterns are matched exactly, or as a prefix when they end in `*`. MCP tools\n * arrive as `mcp__<server>__<tool>`, so listing `mcp__acme` (no trailing star)\n * also allows every tool on the `acme` server.\n */\nexport function evaluateToolPolicy(toolName: string, policy: ToolPolicy): ToolDecision {\n if (matchesAny(toolName, policy.deny)) {\n return { allowed: false, reason: `\"${toolName}\" is on the deny list for this agent.` };\n }\n if (matchesAny(toolName, policy.allow)) {\n return { allowed: true, reason: \"allowed by policy\" };\n }\n if (policy.denyByDefault) {\n return { allowed: false, reason: `\"${toolName}\" is not in this agent's allow list.` };\n }\n return { allowed: true, reason: \"no policy match; default allow\" };\n}\n\nfunction matchesAny(toolName: string, patterns: readonly string[]): boolean {\n return patterns.some((pattern) => matches(toolName, pattern));\n}\n\nfunction matches(toolName: string, pattern: string): boolean {\n if (pattern === \"*\") return true;\n if (pattern.endsWith(\"*\")) return toolName.startsWith(pattern.slice(0, -1));\n if (toolName === pattern) return true;\n // `mcp__acme` covers `mcp__acme__create_workspace`.\n return toolName.startsWith(`${pattern}__`);\n}\n\n/** A short human label for a tool call, for the \"Frankie is …\" line in the UI. */\nexport function describeTool(toolName: string, input: unknown): string {\n const mcp = /^mcp__([^_]+(?:_[^_]+)*)__(.+)$/.exec(toolName);\n if (mcp) {\n const [, server, tool] = mcp;\n return `${humanise(tool ?? toolName)} · ${server}`;\n }\n const inputRecord = typeof input === \"object\" && input !== null ? (input as Record<string, unknown>) : {};\n const hint = inputRecord[\"path\"] ?? inputRecord[\"url\"] ?? inputRecord[\"query\"] ?? inputRecord[\"command\"];\n return typeof hint === \"string\" ? `${humanise(toolName)}: ${truncate(hint, 60)}` : humanise(toolName);\n}\n\nfunction humanise(name: string): string {\n return name\n .replace(/[_-]+/g, \" \")\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .replace(/\\b\\w/g, (c) => c.toUpperCase())\n .trim();\n}\n\nfunction truncate(value: string, max: number): string {\n return value.length <= max ? value : `${value.slice(0, max - 1)}…`;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/tools.ts","../src/install.ts"],"sourcesContent":["import { z } from \"zod\";\n\n/**\n * Tenant configuration. One config == one embedded agent. Everything a\n * customer needs to change to make Frankie theirs lives here, so the code\n * itself stays generic.\n */\n\nexport const widgetThemeSchema = z\n .object({\n primary: z.string().optional(),\n primaryForeground: z.string().optional(),\n background: z.string().optional(),\n foreground: z.string().optional(),\n muted: z.string().optional(),\n border: z.string().optional(),\n radius: z.string().optional(),\n fontFamily: z.string().optional(),\n mode: z.enum([\"light\", \"dark\", \"system\"]).default(\"system\"),\n })\n .prefault({});\n\nexport const thinkingLevelSchema = z.enum([\"minimal\", \"low\", \"medium\", \"high\", \"xhigh\", \"max\"]);\n\nexport const modelConfigSchema = z.object({\n /**\n * Any provider the model layer knows about. `fireworks` is the default;\n * `anthropic`, `openai`, `google`, `groq`, `together`, `openrouter` and\n * ~30 others resolve too, which is what makes the model a config choice.\n */\n provider: z.string().min(1).default(\"fireworks\"),\n /**\n * Model id within that provider. The default is Kimi K2.7 on Fireworks,\n * which serves a native Anthropic-Messages endpoint and a 262K window.\n */\n model: z.string().default(\"accounts/fireworks/models/kimi-k2p7-code\"),\n /** Env var holding the provider key. Never put the key in config itself. */\n apiKeyEnv: z.string().default(\"FIREWORKS_API_KEY\"),\n /** Override the provider's base URL (self-hosted gateways, tests). */\n baseUrl: z.string().url().optional(),\n maxTokens: z.number().int().positive().max(200_000).default(8192),\n temperature: z.number().min(0).max(2).default(0.6),\n /**\n * Reasoning depth. Lowest by default: an onboarding widget is latency-\n * sensitive and its questions rarely need deliberation.\n */\n thinkingLevel: thinkingLevelSchema.default(\"minimal\"),\n /** Forward the model's reasoning stream to the client as thinking deltas. */\n streamReasoning: z.boolean().default(false),\n});\n\n/** One selectable model in the (optional) end-user model picker. */\nexport const modelChoiceSchema = z.object({\n /** Provider model id, e.g. \"accounts/fireworks/models/kimi-k2p7-code\". */\n id: z.string().min(1),\n /** Shown in the picker, e.g. \"Fast\" or \"Smart\". */\n label: z.string().min(1),\n /** Defaults to the workspace's default provider when omitted. */\n provider: z.string().optional(),\n});\n\n/**\n * Optional, opt-in chat capabilities the SDK consumer turns on per workspace.\n * Everything here is OFF by default: an embedded agent only gets a wider surface\n * when the customer deliberately enables it.\n */\nexport const featuresConfigSchema = z\n .object({\n /** Let end users attach files. Images go to a vision model; docs are read as text. */\n attachments: z\n .object({\n enabled: z.boolean().default(false),\n /** Accepted MIME patterns, e.g. \"image/*\", \"text/*\", \"application/json\". */\n accept: z.array(z.string()).default([\"image/*\", \"text/*\", \"application/json\"]),\n maxMb: z.number().positive().max(25).default(10),\n maxCount: z.number().int().positive().max(10).default(4),\n })\n .prefault({}),\n /** Let end users choose the model, from a curated list you control. */\n modelPicker: z\n .object({\n enabled: z.boolean().default(false),\n /** The models offered to end users. The workspace default is always allowed. */\n models: z.array(modelChoiceSchema).default([]),\n })\n .prefault({}),\n /**\n * Platform onboarding: give the agent built-in tools to create and list the\n * signed-in user's own Frankie workspaces. Only for a workspace that *is* the\n * Frankie onboarding assistant — a normal customer never turns this on.\n */\n workspaceAdmin: z.boolean().default(false),\n })\n .prefault({});\n\nexport const mcpServerSchema = z.object({\n name: z\n .string()\n .min(1)\n .regex(/^[a-z0-9][a-z0-9_-]*$/i, \"MCP server names must be alphanumeric with - or _\"),\n enabled: z.boolean().default(true),\n /** Explicit tool selection. Omitted keeps legacy all-tools behavior; [] disables all. */\n selectedTools: z.array(z.string().min(1)).optional(),\n discoveredTools: z.array(z.object({ name: z.string(), description: z.string().optional() })).default([]),\n /** `http` covers streamable HTTP; `sse` is the legacy transport. */\n transport: z.enum([\"http\", \"sse\"]).default(\"http\"),\n url: z.string().url(),\n /** Static headers, e.g. a service token for the company's own MCP server. */\n headers: z.record(z.string(), z.string()).default({}),\n /**\n * When set, the verified end-user JWT is forwarded to the MCP server in this\n * header so the company's tools can act as that user. Use with care: only\n * enable it for MCP servers you control.\n */\n forwardUserTokenAs: z.string().optional(),\n /** Extra claims from the user token to forward as `x-frankie-<claim>`. */\n forwardUserClaims: z.array(z.string()).default([]),\n});\n\n/**\n * An HTTP API described by an OpenAPI document, turned into tools directly.\n *\n * This is the \"no MCP server to write\" path: point Frankie at a spec, name the\n * operations the agent may use, and it builds the tools at startup.\n */\nexport const apiCapabilitySchema = z.object({\n operationId: z.string().min(1),\n method: z.enum([\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"]),\n path: z.string().min(1),\n summary: z.string().optional(),\n});\n\nexport const apiSourceSchema = z.object({\n name: z\n .string()\n .min(1)\n .regex(/^[a-z0-9][a-z0-9_-]*$/i, \"API source names must be alphanumeric with - or _\"),\n /** URL or local path to an OpenAPI 3.x document (JSON or YAML). */\n spec: z.string().min(1),\n /** Overrides the spec's own `servers[0].url`. Required if the spec has none. */\n baseUrl: z.string().url().optional(),\n /**\n * Operation ids the agent may call. Empty means \"every operation that passes\n * the other filters\" — fine for a small internal API, risky for a large one.\n */\n operations: z.array(z.string()).default([]),\n /**\n * Path globs to include, e.g. `/projects/**`. Empty means all paths.\n */\n includePaths: z.array(z.string()).default([]),\n /**\n * HTTP methods the agent may use. Read-only by default: an onboarding agent\n * wired to a whole API should not be able to DELETE on its first day. Widen\n * deliberately, per API.\n */\n methods: z.array(z.enum([\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"])).default([\"GET\"]),\n enabled: z.boolean().default(true),\n /** Explicit operation selection, independent of the discovered inventory. */\n selectedTools: z.array(z.string().min(1)).optional(),\n /** Whether these approved API tools are also served by the workspace MCP. */\n exposeAsMcp: z.boolean().default(true),\n /** A read-only inventory derived from the source for the dashboard. */\n capabilities: z.array(apiCapabilitySchema).default([]),\n /** Static headers, e.g. a service token. */\n headers: z.record(z.string(), z.string()).default({}),\n /**\n * When set, the verified end-user JWT is forwarded in this header, so your\n * API authorises the call exactly as it would any other request from them.\n */\n forwardUserTokenAs: z.string().optional(),\n /** Extra claims from the user token to forward as `x-frankie-<claim>`. */\n forwardUserClaims: z.array(z.string()).default([]),\n /** Per-call timeout. */\n timeoutMs: z.number().int().positive().default(30_000),\n /** Response bodies longer than this are truncated before the model sees them. */\n maxResponseChars: z.number().int().positive().default(8_000),\n});\n\nexport const toolPolicySchema = z.object({\n /**\n * Tool names the agent may call without asking. MCP tools are named\n * `mcp__<server>__<tool>` and generated API tools `api__<name>__<operation>`;\n * naming the prefix alone (`mcp__acme`, `api__acme`) allows all of them.\n */\n allow: z.array(z.string()).default([]),\n /** Explicit denials; evaluated before `allow`. */\n deny: z.array(z.string()).default([]),\n /**\n * Deny anything not in `allow`. On by default — an onboarding agent living\n * on a public marketing page should not have an open tool surface.\n */\n denyByDefault: z.boolean().default(true),\n});\n\nexport const agentConfigSchema = z.object({\n /** Prepended to the runtime's own operating instructions. */\n systemPrompt: z.string().default(\"\"),\n /** First message the widget shows before the user types anything. */\n greeting: z.string().default(\"Hi! I can walk you through getting set up. What are you trying to do?\"),\n suggestedPrompts: z.array(z.string()).max(6).default([]),\n maxTurns: z.number().int().positive().max(50).default(12),\n /** Hard stop on estimated spend per session. */\n maxBudgetUsd: z.number().positive().optional(),\n tools: toolPolicySchema.prefault({}),\n /** Show tool activity in the transcript. */\n showToolActivity: z.boolean().default(true),\n});\n\nexport const authConfigSchema = z.object({\n /**\n * Safe to ship in client bundles. Identifies the tenant; it authorises\n * nothing on its own — the origin allowlist and the user token do that.\n */\n publishableKey: z.string().min(8),\n /** Exact origins allowed to open sessions, e.g. `https://app.acme.com`. */\n allowedOrigins: z.array(z.string()).default([]),\n /** Let visitors chat without a host-signed token (marketing pages). */\n allowAnonymous: z.boolean().default(false),\n /** Env var holding the HS256 secret the host signs user tokens with. */\n jwtSecretEnv: z.string().default(\"FRANKIE_JWT_SECRET\"),\n /** Verify against a JWKS endpoint instead of a shared secret (RS/ES256). */\n jwksUrl: z.string().url().optional(),\n jwtIssuer: z.string().optional(),\n jwtAudience: z.string().optional(),\n /** How long an issued session token is good for. */\n sessionTtlSeconds: z.number().int().positive().default(60 * 60),\n /** Env var holding the secret used to sign Frankie's own session tokens. */\n sessionSecretEnv: z.string().default(\"FRANKIE_SESSION_SECRET\"),\n});\n\nexport const limitsConfigSchema = z.object({\n /** Messages per session per window. */\n messagesPerWindow: z.number().int().positive().default(30),\n windowSeconds: z.number().int().positive().default(15 * 60),\n /** Sessions a single IP may open per window. */\n sessionsPerIpPerWindow: z.number().int().positive().default(20),\n /** Longest a single turn may run before it is aborted. */\n turnTimeoutMs: z.number().int().positive().default(120_000),\n});\n\nexport const brandingConfigSchema = z.object({\n name: z.string().default(\"Assistant\"),\n tagline: z.string().optional(),\n avatarUrl: z.string().url().optional(),\n launcherLabel: z.string().default(\"Need a hand?\"),\n});\n\nexport const frankieConfigSchema = z.object({\n tenantId: z.string().min(1),\n branding: brandingConfigSchema.prefault({}),\n theme: widgetThemeSchema,\n model: modelConfigSchema.prefault({}),\n agent: agentConfigSchema.prefault({}),\n auth: authConfigSchema,\n mcpServers: z.array(mcpServerSchema).default([]),\n /** APIs turned into tools from their OpenAPI document. */\n apis: z.array(apiSourceSchema).default([]),\n limits: limitsConfigSchema.prefault({}),\n /** Opt-in end-user chat capabilities (attachments, model picker). */\n features: featuresConfigSchema,\n});\n\nexport type ThinkingLevel = z.infer<typeof thinkingLevelSchema>;\nexport type WidgetThemeConfig = z.infer<typeof widgetThemeSchema>;\nexport type ModelChoice = z.infer<typeof modelChoiceSchema>;\nexport type FeaturesConfig = z.infer<typeof featuresConfigSchema>;\nexport type ModelConfig = z.infer<typeof modelConfigSchema>;\nexport type McpServerConfig = z.infer<typeof mcpServerSchema>;\nexport type ApiCapability = z.infer<typeof apiCapabilitySchema>;\nexport type ApiSourceConfig = z.infer<typeof apiSourceSchema>;\nexport type ToolPolicy = z.infer<typeof toolPolicySchema>;\nexport type AgentConfig = z.infer<typeof agentConfigSchema>;\nexport type AuthConfig = z.infer<typeof authConfigSchema>;\nexport type LimitsConfig = z.infer<typeof limitsConfigSchema>;\nexport type BrandingConfig = z.infer<typeof brandingConfigSchema>;\nexport type FrankieConfig = z.infer<typeof frankieConfigSchema>;\nexport type FrankieConfigInput = z.input<typeof frankieConfigSchema>;\n\nexport function parseFrankieConfig(input: unknown): FrankieConfig {\n return frankieConfigSchema.parse(input);\n}\n\n/** Helper for `frankie.config.ts` files — gives editors full completion. */\nexport function defineFrankieConfig(config: FrankieConfigInput): FrankieConfigInput {\n return config;\n}\n","import type { ToolPolicy } from \"./config.js\";\n\nexport interface ToolDecision {\n allowed: boolean;\n reason: string;\n}\n\n/**\n * Decide whether the agent may run a tool.\n *\n * Patterns are matched exactly, or as a prefix when they end in `*`. MCP tools\n * arrive as `mcp__<server>__<tool>`, so listing `mcp__acme` (no trailing star)\n * also allows every tool on the `acme` server.\n */\nexport function evaluateToolPolicy(toolName: string, policy: ToolPolicy): ToolDecision {\n if (matchesAny(toolName, policy.deny)) {\n return { allowed: false, reason: `\"${toolName}\" is on the deny list for this agent.` };\n }\n if (matchesAny(toolName, policy.allow)) {\n return { allowed: true, reason: \"allowed by policy\" };\n }\n if (policy.denyByDefault) {\n return { allowed: false, reason: `\"${toolName}\" is not in this agent's allow list.` };\n }\n return { allowed: true, reason: \"no policy match; default allow\" };\n}\n\nfunction matchesAny(toolName: string, patterns: readonly string[]): boolean {\n return patterns.some((pattern) => matches(toolName, pattern));\n}\n\nfunction matches(toolName: string, pattern: string): boolean {\n if (pattern === \"*\") return true;\n if (pattern.endsWith(\"*\")) return toolName.startsWith(pattern.slice(0, -1));\n if (toolName === pattern) return true;\n // `mcp__acme` covers `mcp__acme__create_workspace`.\n return toolName.startsWith(`${pattern}__`);\n}\n\n/** A short human label for a tool call, for the \"Frankie is …\" line in the UI. */\nexport function describeTool(toolName: string, input: unknown): string {\n const mcp = /^mcp__([^_]+(?:_[^_]+)*)__(.+)$/.exec(toolName);\n if (mcp) {\n const [, server, tool] = mcp;\n return `${humanise(tool ?? toolName)} · ${server}`;\n }\n const inputRecord = typeof input === \"object\" && input !== null ? (input as Record<string, unknown>) : {};\n const hint = inputRecord[\"path\"] ?? inputRecord[\"url\"] ?? inputRecord[\"query\"] ?? inputRecord[\"command\"];\n return typeof hint === \"string\" ? `${humanise(toolName)}: ${truncate(hint, 60)}` : humanise(toolName);\n}\n\nfunction humanise(name: string): string {\n return name\n .replace(/[_-]+/g, \" \")\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .replace(/\\b\\w/g, (c) => c.toUpperCase())\n .trim();\n}\n\nfunction truncate(value: string, max: number): string {\n return value.length <= max ? value : `${value.slice(0, max - 1)}…`;\n}\n","/** One installation plan for GitHub and the local CLI. Never includes credentials. */\nexport interface InstallProject {\n files: Record<string, string>;\n workspace: { id: string; name: string; publishableKey: string; allowAnonymous: boolean };\n apiBaseUrl?: string;\n}\nexport interface InstallPlan {\n files: Array<{ path: string; content: string }>; root: string; chatPath: string; specPath: string;\n operationCount: number; needsAuthSetup: boolean; notes: string[];\n}\ntype ApiDocument = { paths: Record<string, Record<string, unknown>>; [key: string]: unknown };\nexport function planHostedInstall(input: InstallProject): InstallPlan {\n const { files, workspace } = input;\n const layouts = Object.keys(files).filter((p) => /(?:^|\\/)(?:src\\/)?app(?:\\/\\([^/]+\\))?\\/layout\\.tsx$/.test(p));\n const rootLayoutPath = layouts.find((p) => !/(?:^|\\/)app\\/\\([^/]+\\)\\/layout\\.tsx$/.test(p)) ?? layouts[0];\n if (!rootLayoutPath) throw new Error(\"Automatic installation supports Next.js App Router. No app/layout.tsx was found.\");\n // Prefer an existing authenticated/dashboard route group for the chat page and\n // floating widget; route groups do not change the URL, but preserve its layout,\n // session gate, and sidebar.\n const layoutPath = layouts.find((p) => /(?:^|\\/)app\\/\\((?:dashboard|authenticated|protected|app)\\)\\/layout\\.tsx$/.test(p)) ?? rootLayoutPath;\n const appRoot = rootLayoutPath.slice(0, -\"layout.tsx\".length);\n const integrationAppRoot = layoutPath.slice(0, -\"layout.tsx\".length);\n const root = appRoot.replace(/(?:src\\/)?app\\/$/, \"\");\n const pkgPath = root + \"package.json\";\n const pkg = JSON.parse(files[pkgPath] ?? \"{}\");\n if (!pkg.dependencies?.next) throw new Error(\"The selected app must have Next.js in dependencies.\");\n const out = new Map<string, string>();\n const notes: string[] = [];\n const existingMount = files[layoutPath]!.includes(\"FrankieWidgetMount\");\n const sourceRoot = appRoot.endsWith(\"src/app/\") ? root + \"src/\" : root;\n const firebase = Boolean(files[sourceRoot + \"lib/firebase-admin.ts\"]?.includes(\"getAdminAuth\") && files[sourceRoot + \"lib/session.ts\"]?.includes(\"SESSION_COOKIE\"));\n const authFile = files[sourceRoot + \"auth.ts\"];\n const authJs = Boolean(authFile?.includes(\"NextAuth\") && /export\\s+(?:const|let)\\s*\\{[^}]*\\bauth\\b/.test(authFile ?? \"\"));\n const tokenPath = appRoot + \"api/frankie/token/route.ts\";\n const needsAuthSetup = !workspace.allowAnonymous && !files[tokenPath] && !firebase && !authJs;\n if (needsAuthSetup) notes.push(\"Connect getCurrentUser in the generated token endpoint to your server session. It rejects requests until configured.\");\n pkg.dependencies = { ...pkg.dependencies, \"@frankieai/react\": pkg.dependencies[\"@frankieai/react\"] ?? \"^0.1.5\", ...(!workspace.allowAnonymous ? { jose: pkg.dependencies.jose ?? \"^6.0.11\" } : {}) };\n out.set(pkgPath, JSON.stringify(pkg, null, 2) + \"\\n\");\n const authProp = workspace.allowAnonymous ? \"\" : \" getUserToken={getUserToken}\";\n // The distributed stylesheet omits Preflight and scopes every rule to .frankie-root,\n // so the full-page chat is polished without changing the host dashboard.\n const shared = `import { FrankieProvider, FrankieWidget, FrankieChatPage } from \"@frankieai/react\";\\nimport \"@frankieai/react/styles.css\";\\n`;\n const getToken = workspace.allowAnonymous ? \"\" : `\nasync function getUserToken() {\n const response = await fetch(\"/api/frankie/token\", { method: \"POST\", credentials: \"same-origin\" });\n if (!response.ok) return undefined;\n return ((await response.json()) as { token?: string }).token;\n}\n`;\n let priorChat: string | undefined;\n try { const previous = JSON.parse(files[root + \"frankie.install.json\"] ?? \"{}\"); if (previous.workspaceId === workspace.id && [\"/chat\", \"/frankie/chat\"].includes(previous.chatPath)) priorChat = previous.chatPath; } catch {}\n const chatPath = priorChat ?? (files[integrationAppRoot + \"chat/page.tsx\"] ? \"/frankie/chat\" : \"/chat\");\n if (!priorChat && files[integrationAppRoot + chatPath.slice(1) + \"/page.tsx\"]) throw new Error(\"Both /chat and /frankie/chat already exist. Choose a chat route manually.\");\n out.set(root + \"frankie.install.json\", JSON.stringify({ workspaceId: workspace.id, chatPath }, null, 2) + \"\\n\");\n if (chatPath !== \"/chat\") notes.push(\"Existing /chat preserved. Frankie chat is available at /frankie/chat.\");\n const mount = `\"use client\";\\nimport { usePathname } from \"next/navigation\";\\n${shared}${getToken}\nexport function FrankieWidgetMount() {\n const pathname = usePathname();\n const key = process.env.NEXT_PUBLIC_FRANKIE_KEY;\n if (!key || pathname === ${JSON.stringify(chatPath)}) return null;\n return <FrankieProvider publishableKey={key}${authProp}><FrankieWidget /></FrankieProvider>;\n}\nexport function WorkspaceChatPage() {\n const key = process.env.NEXT_PUBLIC_FRANKIE_KEY;\n if (!key) return <p>Chat is not configured. Set NEXT_PUBLIC_FRANKIE_KEY in your deployment.</p>;\n return <FrankieChatPage publishableKey={key}${authProp} />;\n}\n`;\n const moduleName = existingMount ? \"FrankieSetupChat\" : \"FrankieWidgetMount\";\n out.set(integrationAppRoot + \"frankie/\" + moduleName + \".tsx\", mount);\n if (!existingMount) {\n const layout = files[layoutPath]!;\n const importLine = `import { FrankieWidgetMount } from \"./frankie/FrankieWidgetMount\";\\n`;\n const withImport = /^[\"']use client[\"'];/.test(layout) ? layout.replace(/^(['\"]use client['\"];)/, \"$1\\n\" + importLine) : importLine + layout;\n if (withImport.includes(\"</body>\")) {\n out.set(layoutPath, withImport.replace(\"</body>\", \" <FrankieWidgetMount />\\n</body>\"));\n } else if (withImport.includes(\"</main>\")) {\n out.set(layoutPath, withImport.replace(\"</main>\", \" <FrankieWidgetMount />\\n</main>\"));\n } else if (withImport.includes(\"{children}\")) {\n out.set(layoutPath, withImport.replace(\"{children}\", \"{children}<FrankieWidgetMount />\"));\n } else {\n throw new Error(\"The selected layout has no safe widget mount point. Add FrankieWidgetMount manually.\");\n }\n }\n const pageImport = chatPath === \"/chat\" ? \"../frankie/\" : \"../../frankie/\";\n out.set(integrationAppRoot + chatPath.slice(1) + \"/page.tsx\", `import { WorkspaceChatPage } from \"${pageImport}${moduleName}\";\\nexport default WorkspaceChatPage;\\n`);\n if (!workspace.allowAnonymous && !files[tokenPath]) {\n const lookup = firebase\n ? `import { cookies } from \"next/headers\";\\nimport { getAdminAuth } from \"@/lib/firebase-admin\";\\nimport { SESSION_COOKIE } from \"@/lib/session\";\\nasync function getCurrentUser() {\\n const cookie = (await cookies()).get(SESSION_COOKIE)?.value;\\n if (!cookie) return null;\\n const user = await getAdminAuth().verifySessionCookie(cookie, true);\\n return { id: user.uid, name: user.name, email: user.email };\\n}\\n`\n : authJs ? `import { auth } from \"@/auth\";\\nasync function getCurrentUser() {\\n const session = await auth();\\n const user = session?.user as { id?: string; name?: string | null; email?: string | null } | undefined;\\n return user?.id ? user : null;\\n}\\n`\n : `// Replace with your app's verified server session lookup.\\nasync function getCurrentUser(): Promise<{ id: string; name?: string; email?: string } | null> { return null; }\\n`;\n out.set(tokenPath, `import { SignJWT } from \"jose\";\\nimport { NextResponse } from \"next/server\";\\n${lookup}\nexport async function POST() {\n const secret = process.env.FRANKIE_JWT_SECRET;\n if (!secret) return NextResponse.json({ error: \"Chat secret not configured\" }, { status: 503 });\n try {\n const user = await getCurrentUser();\n if (!user?.id) return NextResponse.json({ error: \"Sign in required\" }, { status: 401 });\n const token = await new SignJWT({ name: user.name, email: user.email })\n .setProtectedHeader({ alg: \"HS256\" }).setSubject(user.id).setIssuedAt()\n .setExpirationTime(\"5m\").sign(new TextEncoder().encode(secret));\n return NextResponse.json({ token }, { headers: { \"Cache-Control\": \"no-store\" } });\n } catch { return NextResponse.json({ error: \"Sign in required\" }, { status: 401 }); }\n}\n`);\n }\n let doc: ApiDocument | undefined;\n for (const [path, content] of Object.entries(files)) {\n if (!/(?:openapi|swagger)[^/]*\\.json$/i.test(path)) continue;\n try { const parsed = JSON.parse(content); if (parsed.openapi && parsed.paths) { doc = parsed; break; } } catch {}\n }\n const paths: Record<string, Record<string, unknown>> = {};\n if (!doc) for (const [file, content] of Object.entries(files)) {\n if (/\\.(test|spec)\\.[tj]sx?$/.test(file)) continue;\n const nextRoute = file.match(/(?:^|\\/)app(\\/.*)\\/route\\.[tj]s$/);\n if (nextRoute) {\n const path = nextRoute[1]!.replace(/\\/\\([^/]+\\)/g, \"\").replace(/\\[([^\\]]+)\\]/g, \"{$1}\");\n if (path.includes(\"...\") || /\\/(?:auth|frankie)(?:\\/|$)/.test(path)) continue;\n // Generated tool inventories are read-only. Writes require an explicit, reviewed schema.\n for (const method of [\"GET\"]) {\n if (new RegExp(\"export\\\\s+(?:(?:async\\\\s+)?function\\\\s+\" + method + \"\\\\b|(?:const|let)\\\\s+\" + method + \"\\\\s*=)\").test(content))\n addInstallOperation(paths, method, path, file);\n }\n } else if (/\\.[tj]s$/.test(file) && /(?:routes?|api)\\//.test(file)) {\n for (const match of content.matchAll(/\\b(?:app|router|fastify)\\.(get|post|put|patch|delete)(?:\\s*<[^;]{1,1600}?>)?\\s*\\(\\s*[\"']([^\"']+)[\"']/g))\n addInstallOperation(paths, match[1]!.toUpperCase(), match[2]!.replace(/:([A-Za-z0-9_]+)/g, \"{$1}\"), file);\n }\n }\n doc ??= { openapi: \"3.0.3\", info: { title: workspace.name + \" API\", version: \"1.0.0\", description: \"Read-only generated route inventory. Review schemas, authorization, and the deployed origin before enabling tools.\" }, paths };\n const specPath = root + \"public/frankie.openapi.json\";\n const operationCount = Object.values(doc.paths).reduce((n, item) => n + Object.keys(item).filter((m) => /^(get|post|put|patch|delete)$/.test(m)).length, 0);\n out.set(specPath, JSON.stringify(doc, null, 2) + \"\\n\");\n if (!operationCount) notes.push(\"No supported API routes detected. The OpenAPI draft has no operations; add the real schema before connecting tools.\");\n notes.push(\"Generated OpenAPI is read-only (GET only). Add write operations only to a reviewed, user-authorized schema.\");\n notes.push(\"Verify the public spec URL is reachable without dashboard authentication before connecting it.\");\n notes.push(\"The token endpoint issues a signed user JWT, but your product API must validate it and enforce user/resource permissions before exposing tools.\");\n const envPath = root + \".env.example\";\n const env = (files[envPath] ?? \"\").replace(/^NEXT_PUBLIC_FRANKIE_KEY=.*\\n?/gm, \"\").replace(/^FRANKIE_JWT_SECRET=.*\\n?/gm, \"\").trimEnd();\n out.set(envPath, env + \"\\nNEXT_PUBLIC_FRANKIE_KEY=\" + workspace.publishableKey + \"\\n\" + (workspace.allowAnonymous ? \"\" : \"# Server only: use the secret from this workspace. Never commit its value.\\nFRANKIE_JWT_SECRET=\\n\"));\n out.set(root + \"FRANKIE_SETUP.md\", `# Frankie setup\\n\\nWorkspace: ${workspace.name}\\n\\n1. Add NEXT_PUBLIC_FRANKIE_KEY from .env.example to the frontend deployment.\\n${workspace.allowAnonymous ? \"\" : \"2. Add FRANKIE_JWT_SECRET from this workspace to the server hosting /api/frankie/token. Never commit the value.\\n\"}3. Run your package manager and commit the updated lockfile (for pnpm: pnpm install --lockfile-only). The remote GitHub installer cannot safely synthesize a lockfile.\\n4. Confirm https://YOUR_APP/frankie.openapi.json is public. If your Next.js middleware protects all paths, explicitly bypass /frankie.openapi.json before connecting it.\\n5. Open ${chatPath}. It is created inside the detected authenticated dashboard group when one exists; add its link to the host sidebar during review.\\n6. Connect https://YOUR_APP/frankie.openapi.json in Frankie → Tools & MCP. The generated document contains only read-only GET operations.\\n7. Forwarding sends the Frankie-signed JWT. Your API must validate it and enforce user/resource permissions. It is not automatically interchangeable with a Firebase or OAuth access token.\\n8. Add NEXT_PUBLIC_FRANKIE_KEY to every frontend deployment environment (Preview and Production). ${workspace.allowAnonymous ? \"\" : \"Add FRANKIE_JWT_SECRET only to the server environment running /api/frankie/token; never expose it to the browser.\\n\"}9. Review conversations in Workspace → Chat sessions.\\n\\n## Remaining setup\\n\\n${notes.map((n) => \"- \" + n).join(\"\\n\")}\\n`);\n return { files: [...out].map(([path, content]) => ({ path, content })), root, chatPath, specPath, operationCount, needsAuthSetup, notes };\n}\nfunction addInstallOperation(paths: Record<string, Record<string, unknown>>, method: string, path: string, file: string) {\n if (path.includes(\"*\") || !path.startsWith(\"/\")) return;\n (paths[path] ??= {})[method.toLowerCase()] = {\n operationId: (method.toLowerCase() + \"_\" + path).replace(/[^a-zA-Z0-9_]+/g, \"_\"),\n summary: method + \" \" + path, description: \"Detected in \" + file + \". Review schemas before enabling.\",\n parameters: [...path.matchAll(/\\{([^}]+)\\}/g)].map((m) => ({ name: m[1], in: \"path\", required: true, schema: { type: \"string\" } })),\n responses: { \"200\": { description: \"Successful response\" } }, \"x-frankie-review-required\": true,\n };\n}\n"],"mappings":"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","names":[]}
|