@frankieai/core 0.2.3 → 0.2.4

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
@@ -49,6 +49,8 @@ __export(index_exports, {
49
49
  planHostedInstall: () => planHostedInstall,
50
50
  sdkUpdateWorkflow: () => sdkUpdateWorkflow,
51
51
  skillSchema: () => skillSchema,
52
+ starterPromptSchema: () => starterPromptSchema,
53
+ suggestedPromptsFromSkills: () => suggestedPromptsFromSkills,
52
54
  thinkingLevelSchema: () => thinkingLevelSchema,
53
55
  toolDescriptions: () => toolDescriptions,
54
56
  toolPolicySchema: () => toolPolicySchema,
@@ -141,6 +143,36 @@ function toolDescriptions(config, toolIds) {
141
143
  }
142
144
  return toolIds.map((id) => ({ id, description: found.get(id) ?? id }));
143
145
  }
146
+ function suggestedPromptsFromSkills(config) {
147
+ const seen = /* @__PURE__ */ new Set();
148
+ const prompts = [];
149
+ for (const skill of activeSkills(config)) {
150
+ const title = cleanSkillName(skill.name);
151
+ if (!title) continue;
152
+ const key = title.toLowerCase();
153
+ if (seen.has(key)) continue;
154
+ seen.add(key);
155
+ const detail = summarizeGoal(skill.goal, title);
156
+ prompts.push(detail ? `${title} \u2014 ${detail}` : title);
157
+ if (prompts.length >= 6) break;
158
+ }
159
+ return prompts;
160
+ }
161
+ function cleanSkillName(name) {
162
+ const trimmed = name.trim();
163
+ const withoutSource = trimmed.replace(/^[^·/:]{1,40}?\s*[·/:]\s*/, "");
164
+ const cleaned = (withoutSource || trimmed).replace(/[·/_]+/g, " ").replace(/\s+/g, " ").trim();
165
+ return cleaned ? cleaned.charAt(0).toUpperCase() + cleaned.slice(1) : "";
166
+ }
167
+ function summarizeGoal(goal, title) {
168
+ let cleaned = goal.trim().replace(/^use this(?: skill)?(?: when (?:the user (?:asks?(?: you)?(?: to| for| about)?|wants?(?: to)?|needs?(?: to)?)|you (?:need|want) to)| to)\s+/i, "").replace(/\s+/g, " ").replace(/[.\s]+$/, "");
169
+ const restatement = cleaned.match(/^.{1,40}?\s+[—–-]\s+(.+)$/);
170
+ if (restatement) cleaned = restatement[1];
171
+ if (/^(listing|creating|reading|updating|managing)\b/i.test(cleaned) || /^(create|read|update|delete|list|manage),/i.test(cleaned)) return "";
172
+ if (!cleaned || cleaned.toLowerCase() === title.toLowerCase()) return "";
173
+ const capped = cleaned.length > 80 ? cleaned.slice(0, 80).replace(/\s+\S*$/, "") + "\u2026" : cleaned;
174
+ return capped.charAt(0).toUpperCase() + capped.slice(1);
175
+ }
144
176
  function draftSkill(goal, tools) {
145
177
  return [
146
178
  `Use this when: ${goal}`,
@@ -393,12 +425,23 @@ var toolPolicySchema = import_zod2.z.object({
393
425
  */
394
426
  denyByDefault: import_zod2.z.boolean().default(true)
395
427
  });
428
+ var starterPromptSchema = import_zod2.z.object({
429
+ title: import_zod2.z.string().trim().min(1).max(80),
430
+ description: import_zod2.z.string().trim().max(160).optional(),
431
+ message: import_zod2.z.string().trim().min(1).max(2e3)
432
+ });
396
433
  var agentConfigSchema = import_zod2.z.object({
397
434
  /** Prepended to the runtime's own operating instructions. */
398
435
  systemPrompt: import_zod2.z.string().default(""),
399
436
  /** First message the widget shows before the user types anything. */
400
437
  greeting: import_zod2.z.string().default("Hi! I can walk you through getting set up. What are you trying to do?"),
401
438
  suggestedPrompts: import_zod2.z.array(import_zod2.z.string()).max(6).default([]),
439
+ /**
440
+ * Explicit starter cards for the empty chat screen (title + description +
441
+ * the message clicking sends). When set, these take precedence over
442
+ * `suggestedPrompts` and over any skill-derived defaults.
443
+ */
444
+ starterPrompts: import_zod2.z.array(starterPromptSchema).max(6).default([]),
402
445
  maxTurns: import_zod2.z.number().int().positive().max(50).default(12),
403
446
  /** Hard stop on estimated spend per session. */
404
447
  maxBudgetUsd: import_zod2.z.number().positive().optional(),
@@ -549,6 +592,17 @@ async function getUserToken() {
549
592
  throw new Error("The chat sign-in endpoint returned " + response.status + ".");
550
593
  }
551
594
  `;
595
+ const hostApiHeaders = auth?.client && !workspace.allowAnonymous ? `
596
+ headers: async () => {
597
+ const session = (await ${auth.client.name}?.auth.getSession())?.data.session;
598
+ return session?.access_token ? { Authorization: \`Bearer \${session.access_token}\` } : {};
599
+ },` : "";
600
+ const hostApi = `const hostApi = {
601
+ baseUrl: typeof window !== "undefined" ? window.location.origin : "",
602
+ credentials: "${auth?.client || workspace.allowAnonymous ? "same-origin" : "include"}" as const,${hostApiHeaders}
603
+ };
604
+ `;
605
+ const apiProp = " api={hostApi}";
552
606
  let priorChat;
553
607
  try {
554
608
  const previous = JSON.parse(files[root + "frankie.install.json"] ?? "{}");
@@ -561,17 +615,17 @@ async function getUserToken() {
561
615
  if (chatPath !== "/chat") notes.push("Existing /chat preserved. Frankie chat is available at /frankie/chat.");
562
616
  const mount = `"use client";
563
617
  import { usePathname } from "next/navigation";
564
- ${shared}${clientAuthImport}${getToken}
618
+ ${shared}${clientAuthImport}${getToken}${hostApi}
565
619
  export function FrankieWidgetMount() {
566
620
  const pathname = usePathname();
567
621
  const key = process.env.NEXT_PUBLIC_FRANKIE_KEY;
568
622
  if (!key || pathname === ${JSON.stringify(chatPath)}) return null;
569
- return <FrankieProvider publishableKey={key}${authProp}><FrankieWidget /></FrankieProvider>;
623
+ return <FrankieProvider publishableKey={key}${authProp}${apiProp}><FrankieWidget /></FrankieProvider>;
570
624
  }
571
625
  export function WorkspaceChatPage() {
572
626
  const key = process.env.NEXT_PUBLIC_FRANKIE_KEY;
573
627
  if (!key) return <p>Chat is not configured. Set NEXT_PUBLIC_FRANKIE_KEY in your deployment.</p>;
574
- return <FrankieChatPage publishableKey={key}${authProp} />;
628
+ return <FrankieChatPage publishableKey={key}${authProp}${apiProp} />;
575
629
  }
576
630
  `;
577
631
  const moduleName = existingMount ? "FrankieSetupChat" : "FrankieWidgetMount";
@@ -682,7 +736,7 @@ export async function GET() {
682
736
  if (!operationCount) notes.push("No supported API routes detected. The OpenAPI draft has no operations; add the real schema before connecting tools.");
683
737
  if (withheld) notes.push(`Withheld ${withheld} newly discovered write operation(s) with no detected authorization guard (no Express/Fastify middleware argument, Next.js middleware.ts matcher covering the route, or in-handler auth check). Add them to the spec yourself once they're protected.`);
684
738
  notes.push("Generated OpenAPI includes read and write routes. Review schemas before approving tools; new connections still default to GET-only access.");
685
- notes.push("Verify the public spec URL is reachable without dashboard authentication before connecting it.");
739
+ notes.push("No public spec endpoint is required: Frankie connects this committed spec on install and reads later changes from your repository through the GitHub connection.");
686
740
  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.");
687
741
  const envPath = root + ".env.example";
688
742
  const env = (files[envPath] ?? "").replace(/^NEXT_PUBLIC_FRANKIE_KEY=.*\n?/gm, "").replace(/^FRANKIE_JWT_SECRET=.*\n?/gm, "").trimEnd();
@@ -693,9 +747,9 @@ Workspace: ${workspace.name}
693
747
 
694
748
  1. Copy NEXT_PUBLIC_FRANKIE_KEY from Frankie \u2192 Workspace \u2192 Developer details into the frontend deployment environment. Frankie deliberately leaves the value blank in this PR.
695
749
  ${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.
696
- 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.
750
+ 4. Nothing to expose: this PR commits your spec to the repository, Frankie connects it for you on install, and later spec changes are read from your repository through the GitHub connection \u2014 no public spec endpoint or middleware bypass is required.
697
751
  5. Open ${chatPath}. It is created inside the detected authenticated dashboard group when one exists; add its link to the host sidebar during review.
698
- 6. Connect https://YOUR_APP/frankie.openapi.json in Frankie \u2192 Tools & MCP. The document includes read and write routes; approve tools and choose access limits separately.
752
+ 6. In Frankie \u2192 Tools & MCP, approve the operations you want and choose access limits (the spec is already connected from this PR; it includes read and write routes). After merging later spec changes, use GitHub integration \u2192 Refresh Frankie from merged spec.
699
753
  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.
700
754
  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.
701
755
 
@@ -1086,6 +1140,8 @@ function compareApiDocuments(before, after) {
1086
1140
  planHostedInstall,
1087
1141
  sdkUpdateWorkflow,
1088
1142
  skillSchema,
1143
+ starterPromptSchema,
1144
+ suggestedPromptsFromSkills,
1089
1145
  thinkingLevelSchema,
1090
1146
  toolDescriptions,
1091
1147
  toolPolicySchema,