@frankieai/core 0.2.3 → 0.2.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 +94 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +100 -1
- package/dist/index.d.ts +100 -1
- package/dist/index.js +90 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -21,6 +21,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
FIREWORKS_MODELS: () => FIREWORKS_MODELS,
|
|
24
|
+
SKILL_TOOL_NAME: () => SKILL_TOOL_NAME,
|
|
25
|
+
STARTER_PROMPT_ICONS: () => STARTER_PROMPT_ICONS,
|
|
24
26
|
activeSkills: () => activeSkills,
|
|
25
27
|
agentConfigSchema: () => agentConfigSchema,
|
|
26
28
|
apiCapabilitySchema: () => apiCapabilitySchema,
|
|
@@ -49,6 +51,8 @@ __export(index_exports, {
|
|
|
49
51
|
planHostedInstall: () => planHostedInstall,
|
|
50
52
|
sdkUpdateWorkflow: () => sdkUpdateWorkflow,
|
|
51
53
|
skillSchema: () => skillSchema,
|
|
54
|
+
starterPromptSchema: () => starterPromptSchema,
|
|
55
|
+
suggestedPromptsFromSkills: () => suggestedPromptsFromSkills,
|
|
52
56
|
thinkingLevelSchema: () => thinkingLevelSchema,
|
|
53
57
|
toolDescriptions: () => toolDescriptions,
|
|
54
58
|
toolPolicySchema: () => toolPolicySchema,
|
|
@@ -85,12 +89,24 @@ function matches(toolName, pattern) {
|
|
|
85
89
|
if (toolName === pattern) return true;
|
|
86
90
|
return toolName.startsWith(`${pattern}__`);
|
|
87
91
|
}
|
|
92
|
+
var SKILL_TOOL_NAME = "frankie__use_skill";
|
|
88
93
|
function describeTool(toolName, input) {
|
|
94
|
+
if (toolName === SKILL_TOOL_NAME) {
|
|
95
|
+
const skill = typeof input === "object" && input !== null ? input["skill"] : void 0;
|
|
96
|
+
return typeof skill === "string" && skill.trim() ? `Using skill: ${skill.trim()}` : "Using a skill";
|
|
97
|
+
}
|
|
89
98
|
const mcp = /^mcp__([^_]+(?:_[^_]+)*)__(.+)$/.exec(toolName);
|
|
90
99
|
if (mcp) {
|
|
91
100
|
const [, server, tool] = mcp;
|
|
92
101
|
return `${humanise(tool ?? toolName)} \xB7 ${server}`;
|
|
93
102
|
}
|
|
103
|
+
if (toolName.startsWith("api__")) {
|
|
104
|
+
const rest = toolName.slice("api__".length);
|
|
105
|
+
const sep = rest.indexOf("__");
|
|
106
|
+
const operation = (sep >= 0 ? rest.slice(sep + 2) : rest).replace(/__+/g, "_");
|
|
107
|
+
const verb = /^(get|post|put|patch|delete|head|options)_(.+)$/i.exec(operation);
|
|
108
|
+
return verb ? `${verb[1].toUpperCase()} ${humanise(verb[2])}` : humanise(operation);
|
|
109
|
+
}
|
|
94
110
|
const inputRecord = typeof input === "object" && input !== null ? input : {};
|
|
95
111
|
const hint = inputRecord["path"] ?? inputRecord["url"] ?? inputRecord["query"] ?? inputRecord["command"];
|
|
96
112
|
return typeof hint === "string" ? `${humanise(toolName)}: ${truncate(hint, 60)}` : humanise(toolName);
|
|
@@ -141,6 +157,36 @@ function toolDescriptions(config, toolIds) {
|
|
|
141
157
|
}
|
|
142
158
|
return toolIds.map((id) => ({ id, description: found.get(id) ?? id }));
|
|
143
159
|
}
|
|
160
|
+
function suggestedPromptsFromSkills(config) {
|
|
161
|
+
const seen = /* @__PURE__ */ new Set();
|
|
162
|
+
const prompts = [];
|
|
163
|
+
for (const skill of activeSkills(config)) {
|
|
164
|
+
const title = cleanSkillName(skill.name);
|
|
165
|
+
if (!title) continue;
|
|
166
|
+
const key = title.toLowerCase();
|
|
167
|
+
if (seen.has(key)) continue;
|
|
168
|
+
seen.add(key);
|
|
169
|
+
const detail = summarizeGoal(skill.goal, title);
|
|
170
|
+
prompts.push(detail ? `${title} \u2014 ${detail}` : title);
|
|
171
|
+
if (prompts.length >= 6) break;
|
|
172
|
+
}
|
|
173
|
+
return prompts;
|
|
174
|
+
}
|
|
175
|
+
function cleanSkillName(name) {
|
|
176
|
+
const trimmed = name.trim();
|
|
177
|
+
const withoutSource = trimmed.replace(/^[^·/:]{1,40}?\s*[·/:]\s*/, "");
|
|
178
|
+
const cleaned = (withoutSource || trimmed).replace(/[·/_]+/g, " ").replace(/\s+/g, " ").trim();
|
|
179
|
+
return cleaned ? cleaned.charAt(0).toUpperCase() + cleaned.slice(1) : "";
|
|
180
|
+
}
|
|
181
|
+
function summarizeGoal(goal, title) {
|
|
182
|
+
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]+$/, "");
|
|
183
|
+
const restatement = cleaned.match(/^.{1,40}?\s+[—–-]\s+(.+)$/);
|
|
184
|
+
if (restatement) cleaned = restatement[1];
|
|
185
|
+
if (/^(listing|creating|reading|updating|managing)\b/i.test(cleaned) || /^(create|read|update|delete|list|manage),/i.test(cleaned)) return "";
|
|
186
|
+
if (!cleaned || cleaned.toLowerCase() === title.toLowerCase()) return "";
|
|
187
|
+
const capped = cleaned.length > 80 ? cleaned.slice(0, 80).replace(/\s+\S*$/, "") + "\u2026" : cleaned;
|
|
188
|
+
return capped.charAt(0).toUpperCase() + capped.slice(1);
|
|
189
|
+
}
|
|
144
190
|
function draftSkill(goal, tools) {
|
|
145
191
|
return [
|
|
146
192
|
`Use this when: ${goal}`,
|
|
@@ -393,12 +439,39 @@ var toolPolicySchema = import_zod2.z.object({
|
|
|
393
439
|
*/
|
|
394
440
|
denyByDefault: import_zod2.z.boolean().default(true)
|
|
395
441
|
});
|
|
442
|
+
var STARTER_PROMPT_ICONS = [
|
|
443
|
+
"sparkles",
|
|
444
|
+
"wand",
|
|
445
|
+
"book",
|
|
446
|
+
"chat",
|
|
447
|
+
"chart",
|
|
448
|
+
"bolt",
|
|
449
|
+
"music",
|
|
450
|
+
"mic",
|
|
451
|
+
"image",
|
|
452
|
+
"compass",
|
|
453
|
+
"lightbulb",
|
|
454
|
+
"file"
|
|
455
|
+
];
|
|
456
|
+
var starterPromptSchema = import_zod2.z.object({
|
|
457
|
+
title: import_zod2.z.string().trim().min(1).max(80),
|
|
458
|
+
description: import_zod2.z.string().trim().max(160).optional(),
|
|
459
|
+
message: import_zod2.z.string().trim().min(1).max(2e3),
|
|
460
|
+
/** One of STARTER_PROMPT_ICONS; unknown values fall back to a default icon. */
|
|
461
|
+
icon: import_zod2.z.enum(STARTER_PROMPT_ICONS).optional()
|
|
462
|
+
});
|
|
396
463
|
var agentConfigSchema = import_zod2.z.object({
|
|
397
464
|
/** Prepended to the runtime's own operating instructions. */
|
|
398
465
|
systemPrompt: import_zod2.z.string().default(""),
|
|
399
466
|
/** First message the widget shows before the user types anything. */
|
|
400
467
|
greeting: import_zod2.z.string().default("Hi! I can walk you through getting set up. What are you trying to do?"),
|
|
401
468
|
suggestedPrompts: import_zod2.z.array(import_zod2.z.string()).max(6).default([]),
|
|
469
|
+
/**
|
|
470
|
+
* Explicit starter cards for the empty chat screen (title + description +
|
|
471
|
+
* the message clicking sends). When set, these take precedence over
|
|
472
|
+
* `suggestedPrompts` and over any skill-derived defaults.
|
|
473
|
+
*/
|
|
474
|
+
starterPrompts: import_zod2.z.array(starterPromptSchema).max(6).default([]),
|
|
402
475
|
maxTurns: import_zod2.z.number().int().positive().max(50).default(12),
|
|
403
476
|
/** Hard stop on estimated spend per session. */
|
|
404
477
|
maxBudgetUsd: import_zod2.z.number().positive().optional(),
|
|
@@ -549,6 +622,17 @@ async function getUserToken() {
|
|
|
549
622
|
throw new Error("The chat sign-in endpoint returned " + response.status + ".");
|
|
550
623
|
}
|
|
551
624
|
`;
|
|
625
|
+
const hostApiHeaders = auth?.client && !workspace.allowAnonymous ? `
|
|
626
|
+
headers: async () => {
|
|
627
|
+
const session = (await ${auth.client.name}?.auth.getSession())?.data.session;
|
|
628
|
+
return session?.access_token ? { Authorization: \`Bearer \${session.access_token}\` } : {};
|
|
629
|
+
},` : "";
|
|
630
|
+
const hostApi = `const hostApi = {
|
|
631
|
+
baseUrl: typeof window !== "undefined" ? window.location.origin : "",
|
|
632
|
+
credentials: "${auth?.client || workspace.allowAnonymous ? "same-origin" : "include"}" as const,${hostApiHeaders}
|
|
633
|
+
};
|
|
634
|
+
`;
|
|
635
|
+
const apiProp = " api={hostApi}";
|
|
552
636
|
let priorChat;
|
|
553
637
|
try {
|
|
554
638
|
const previous = JSON.parse(files[root + "frankie.install.json"] ?? "{}");
|
|
@@ -561,17 +645,17 @@ async function getUserToken() {
|
|
|
561
645
|
if (chatPath !== "/chat") notes.push("Existing /chat preserved. Frankie chat is available at /frankie/chat.");
|
|
562
646
|
const mount = `"use client";
|
|
563
647
|
import { usePathname } from "next/navigation";
|
|
564
|
-
${shared}${clientAuthImport}${getToken}
|
|
648
|
+
${shared}${clientAuthImport}${getToken}${hostApi}
|
|
565
649
|
export function FrankieWidgetMount() {
|
|
566
650
|
const pathname = usePathname();
|
|
567
651
|
const key = process.env.NEXT_PUBLIC_FRANKIE_KEY;
|
|
568
652
|
if (!key || pathname === ${JSON.stringify(chatPath)}) return null;
|
|
569
|
-
return <FrankieProvider publishableKey={key}${authProp}><FrankieWidget /></FrankieProvider>;
|
|
653
|
+
return <FrankieProvider publishableKey={key}${authProp}${apiProp}><FrankieWidget /></FrankieProvider>;
|
|
570
654
|
}
|
|
571
655
|
export function WorkspaceChatPage() {
|
|
572
656
|
const key = process.env.NEXT_PUBLIC_FRANKIE_KEY;
|
|
573
657
|
if (!key) return <p>Chat is not configured. Set NEXT_PUBLIC_FRANKIE_KEY in your deployment.</p>;
|
|
574
|
-
return <FrankieChatPage publishableKey={key}${authProp} />;
|
|
658
|
+
return <FrankieChatPage publishableKey={key}${authProp}${apiProp} />;
|
|
575
659
|
}
|
|
576
660
|
`;
|
|
577
661
|
const moduleName = existingMount ? "FrankieSetupChat" : "FrankieWidgetMount";
|
|
@@ -682,7 +766,7 @@ export async function GET() {
|
|
|
682
766
|
if (!operationCount) notes.push("No supported API routes detected. The OpenAPI draft has no operations; add the real schema before connecting tools.");
|
|
683
767
|
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
768
|
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("
|
|
769
|
+
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
770
|
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
771
|
const envPath = root + ".env.example";
|
|
688
772
|
const env = (files[envPath] ?? "").replace(/^NEXT_PUBLIC_FRANKIE_KEY=.*\n?/gm, "").replace(/^FRANKIE_JWT_SECRET=.*\n?/gm, "").trimEnd();
|
|
@@ -693,9 +777,9 @@ Workspace: ${workspace.name}
|
|
|
693
777
|
|
|
694
778
|
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
779
|
${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.
|
|
780
|
+
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
781
|
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.
|
|
782
|
+
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
783
|
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
784
|
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
785
|
|
|
@@ -1058,6 +1142,8 @@ function compareApiDocuments(before, after) {
|
|
|
1058
1142
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1059
1143
|
0 && (module.exports = {
|
|
1060
1144
|
FIREWORKS_MODELS,
|
|
1145
|
+
SKILL_TOOL_NAME,
|
|
1146
|
+
STARTER_PROMPT_ICONS,
|
|
1061
1147
|
activeSkills,
|
|
1062
1148
|
agentConfigSchema,
|
|
1063
1149
|
apiCapabilitySchema,
|
|
@@ -1086,6 +1172,8 @@ function compareApiDocuments(before, after) {
|
|
|
1086
1172
|
planHostedInstall,
|
|
1087
1173
|
sdkUpdateWorkflow,
|
|
1088
1174
|
skillSchema,
|
|
1175
|
+
starterPromptSchema,
|
|
1176
|
+
suggestedPromptsFromSkills,
|
|
1089
1177
|
thinkingLevelSchema,
|
|
1090
1178
|
toolDescriptions,
|
|
1091
1179
|
toolPolicySchema,
|