@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 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
@@ -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
- export { type AgentConfig, 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 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, apiSourceSchema, authConfigSchema, brandingConfigSchema, defineFrankieConfig, describeTool, evaluateToolPolicy, featuresConfigSchema, frankieConfigSchema, limitsConfigSchema, mcpServerSchema, modelChoiceSchema, modelConfigSchema, parseFrankieConfig, thinkingLevelSchema, toolPolicySchema, widgetThemeSchema };
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
- export { type AgentConfig, 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 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, apiSourceSchema, authConfigSchema, brandingConfigSchema, defineFrankieConfig, describeTool, evaluateToolPolicy, featuresConfigSchema, frankieConfigSchema, limitsConfigSchema, mcpServerSchema, modelChoiceSchema, modelConfigSchema, parseFrankieConfig, thinkingLevelSchema, toolPolicySchema, widgetThemeSchema };
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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frankieai/core",
3
- "version": "0.1.1",
3
+ "version": "0.1.5",
4
4
  "description": "Shared config schema and wire protocol for Frankie.",
5
5
  "license": "MIT",
6
6
  "type": "module",