@muxoai/platform 1.2.3 → 1.3.1
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/.agents/plugins/marketplace.json +1 -1
- package/.claude-plugin/marketplace.json +19 -4
- package/.codex/config.toml +9 -2
- package/README.md +72 -28
- package/dist/cli.js +1047 -43
- package/dist/engine.d.ts +29 -0
- package/dist/engine.js +221 -16
- package/dist/mcp.d.ts +6 -1
- package/dist/mcp.js +120 -26
- package/dist/nl.d.ts +34 -0
- package/dist/nl.js +601 -0
- package/dist/providers/composio-instant.d.ts +1 -0
- package/dist/providers/composio-instant.js +6 -5
- package/dist/providers/composio-sdk.d.ts +1 -0
- package/dist/providers/composio-sdk.js +6 -2
- package/dist/providers/stripe-billing.d.ts +13 -3
- package/dist/providers/stripe-billing.js +250 -36
- package/dist/providers/stripe-projects.js +61 -4
- package/dist/providers/supermemory.d.ts +2 -0
- package/dist/providers/supermemory.js +58 -40
- package/dist/sdk.d.ts +77 -1
- package/dist/sdk.js +46 -0
- package/dist/server.js +202 -29
- package/dist/store.d.ts +19 -2
- package/dist/store.js +216 -11
- package/dist/types.d.ts +14 -2
- package/dist/ui/console-html.js +329 -119
- package/package.json +1 -1
- package/plugins/muxo/.app.json +1 -1
- package/plugins/muxo/.claude-plugin/plugin.json +2 -2
- package/plugins/muxo/.codex-plugin/plugin.json +7 -5
- package/plugins/muxo/.mcp.json +1 -33
- package/plugins/muxo/assets/logo.png +0 -0
- package/plugins/muxo/hooks/session-start.sh +1 -1
- package/plugins/muxo/plugin.json +63 -6
- package/plugins/muxo/skills/muxo/SKILL.md +92 -14
- package/plugins/muxo/skills/muxo/agents/openai.yaml +12 -0
- package/plugins/muxo/skills/muxo/references/cli.md +4 -2
- package/plugins/muxo/skills/muxo/references/mcp.md +15 -9
- package/plugins/muxo/skills/setup/SKILL.md +8 -2
- package/plugins/muxo/skills/setup/agents/openai.yaml +12 -0
package/dist/nl.js
ADDED
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
function extractUrlFromText(text) {
|
|
2
|
+
const fullMatch = text.match(/https?:\/\/[^\s"'<>]+/i);
|
|
3
|
+
if (fullMatch) {
|
|
4
|
+
return fullMatch[0].replace(/[.,;!?)]+$/, "");
|
|
5
|
+
}
|
|
6
|
+
const domainMatch = text.match(/\b((?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+(?:ai|com|io|dev|org|net|app|co|xyz)(?:\/[^\s"'<>]*)?)\b/i);
|
|
7
|
+
if (domainMatch && !text.includes(`@${domainMatch[1]}`)) {
|
|
8
|
+
return `https://${domainMatch[1].replace(/[.,;!?)]+$/, "")}`;
|
|
9
|
+
}
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
function extractEmailFromText(text) {
|
|
13
|
+
const m = text.match(/\b([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})\b/);
|
|
14
|
+
return m ? m[1] : null;
|
|
15
|
+
}
|
|
16
|
+
function extractNumberAfterKeyword(text, keywords, fallback) {
|
|
17
|
+
for (const kw of keywords) {
|
|
18
|
+
const reAfter = new RegExp(`\\b${kw}\\s+(?:of\\s+|to\\s+|=\\s*)?(\\d[\\d,]*)`, "i");
|
|
19
|
+
const m1 = text.match(reAfter);
|
|
20
|
+
if (m1) {
|
|
21
|
+
const n = Number(m1[1].replace(/,/g, ""));
|
|
22
|
+
if (!Number.isNaN(n) && n > 0)
|
|
23
|
+
return n;
|
|
24
|
+
}
|
|
25
|
+
const reBefore = new RegExp(`(\\d[\\d,]*)\\s+${kw}\\b`, "i");
|
|
26
|
+
const m2 = text.match(reBefore);
|
|
27
|
+
if (m2) {
|
|
28
|
+
const n = Number(m2[1].replace(/,/g, ""));
|
|
29
|
+
if (!Number.isNaN(n) && n > 0)
|
|
30
|
+
return n;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return fallback;
|
|
34
|
+
}
|
|
35
|
+
export function extractParametersFromNaturalLanguage(prompt, tool, explicitInput) {
|
|
36
|
+
const input = { ...(explicitInput || {}) };
|
|
37
|
+
const slug = tool.slug;
|
|
38
|
+
const text = prompt.trim();
|
|
39
|
+
if (slug === "instant.seo.audit") {
|
|
40
|
+
if (!input.url) {
|
|
41
|
+
const url = extractUrlFromText(text);
|
|
42
|
+
if (url)
|
|
43
|
+
input.url = url;
|
|
44
|
+
}
|
|
45
|
+
if (input.checkBrokenLinks === undefined) {
|
|
46
|
+
input.checkBrokenLinks = !/\b(skip|no)\s+broken\s+links\b/i.test(text);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else if (slug === "instant.web.crawl") {
|
|
50
|
+
if (!input.url) {
|
|
51
|
+
const url = extractUrlFromText(text);
|
|
52
|
+
if (url)
|
|
53
|
+
input.url = url;
|
|
54
|
+
}
|
|
55
|
+
if (input.maxPages === undefined) {
|
|
56
|
+
input.maxPages = extractNumberAfterKeyword(text, ["maxPages", "pages", "limit"], 5);
|
|
57
|
+
}
|
|
58
|
+
if (input.maxDepth === undefined) {
|
|
59
|
+
input.maxDepth = extractNumberAfterKeyword(text, ["maxDepth", "depth"], 2);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else if (slug === "instant.web.scrape") {
|
|
63
|
+
if (!input.url) {
|
|
64
|
+
const url = extractUrlFromText(text);
|
|
65
|
+
if (url)
|
|
66
|
+
input.url = url;
|
|
67
|
+
}
|
|
68
|
+
if (!input.formats) {
|
|
69
|
+
input.formats = ["markdown"];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else if (slug === "instant.web.search") {
|
|
73
|
+
if (!input.query) {
|
|
74
|
+
const cleaned = text
|
|
75
|
+
.replace(/^(?:please\s+)?(?:can\s+you\s+)?(?:search\s+(?:the\s+)?web\s+(?:for|about)|search\s+(?:for|about)|find\s+(?:info|information|articles)\s+(?:on|about|for)|google|lookup|look\s+up)\s+/i, "")
|
|
76
|
+
.trim();
|
|
77
|
+
input.query = cleaned || text;
|
|
78
|
+
}
|
|
79
|
+
if (input.numResults === undefined) {
|
|
80
|
+
input.numResults = extractNumberAfterKeyword(text, ["results", "limit", "top"], 5);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else if (slug === "instant.code.execute" || slug === "instant.composio.workbench") {
|
|
84
|
+
if (!input.language && slug === "instant.code.execute") {
|
|
85
|
+
input.language = /\b(javascript|js|node|typescript|ts)\b/i.test(text)
|
|
86
|
+
? "javascript"
|
|
87
|
+
: "python";
|
|
88
|
+
}
|
|
89
|
+
const codeBlock = text.match(/```(?:\w+)?\n([\s\S]*?)```/);
|
|
90
|
+
const inlineCode = text.match(/`([^`]+)`/);
|
|
91
|
+
const extractedCode = codeBlock
|
|
92
|
+
? codeBlock[1].trim()
|
|
93
|
+
: inlineCode
|
|
94
|
+
? inlineCode[1].trim()
|
|
95
|
+
: text;
|
|
96
|
+
if (slug === "instant.composio.workbench") {
|
|
97
|
+
if (!input.code_to_execute) {
|
|
98
|
+
input.code_to_execute = String(input.code || extractedCode);
|
|
99
|
+
}
|
|
100
|
+
if (!input.thought) {
|
|
101
|
+
input.thought = `Natural language workbench execution: ${text.slice(0, 80)}`;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else if (!input.code) {
|
|
105
|
+
input.code = extractedCode;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else if (slug === "instant.github.repo_intel") {
|
|
109
|
+
const repoMatch = text.match(/\b([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+)\b/);
|
|
110
|
+
if (repoMatch && !repoMatch[0].includes(".ai") && !repoMatch[0].includes(".com")) {
|
|
111
|
+
if (!input.owner)
|
|
112
|
+
input.owner = repoMatch[1];
|
|
113
|
+
if (!input.repo)
|
|
114
|
+
input.repo = repoMatch[2];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else if (slug.startsWith("resource.")) {
|
|
118
|
+
if (!input.resource_name && !input.name) {
|
|
119
|
+
const namedMatch = text.match(/\b(?:named|called|name|resource)\s+["']?([a-zA-Z0-9_-]+)["']?/i);
|
|
120
|
+
const defaultPrefix = slug.split(".")[1] || "app";
|
|
121
|
+
const resolvedName = namedMatch ? namedMatch[1] : `${defaultPrefix}-main`;
|
|
122
|
+
input.resource_name = resolvedName;
|
|
123
|
+
}
|
|
124
|
+
else if (input.name && !input.resource_name) {
|
|
125
|
+
input.resource_name = input.name;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else if (slug === "billing.credits.topup") {
|
|
129
|
+
if (input.credits === undefined) {
|
|
130
|
+
input.credits = extractNumberAfterKeyword(text, ["credits", "credit", "pack", "topup", "top-up", "add"], 1000);
|
|
131
|
+
}
|
|
132
|
+
if (!input.description) {
|
|
133
|
+
input.description = `Natural language top-up: ${text.slice(0, 80)}`;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
else if (slug === "billing.subscription.upgrade") {
|
|
137
|
+
if (!input.tier) {
|
|
138
|
+
input.tier = "starter";
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else if (slug === "billing.checkout.create") {
|
|
142
|
+
if (!input.kind) {
|
|
143
|
+
input.kind = /\b(starter|subscription|upgrade|monthly|plan)\b/i.test(text)
|
|
144
|
+
? "starter_subscription"
|
|
145
|
+
: "credit_topup";
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
else if (slug === "app.gmail.send_email") {
|
|
149
|
+
if (!input.recipient_email) {
|
|
150
|
+
const email = extractEmailFromText(text);
|
|
151
|
+
if (email)
|
|
152
|
+
input.recipient_email = email;
|
|
153
|
+
}
|
|
154
|
+
if (!input.subject) {
|
|
155
|
+
const subjMatch = text.match(/\bsubject\s*[:=]?\s*["']([^"']+)["']|\bsubject\s+([^\n]+?)(?:\s+body\b|\s+saying\b|$)/i);
|
|
156
|
+
input.subject = subjMatch
|
|
157
|
+
? (subjMatch[1] || subjMatch[2]).trim()
|
|
158
|
+
: "Message from Muxo Agent";
|
|
159
|
+
}
|
|
160
|
+
if (!input.body) {
|
|
161
|
+
const bodyMatch = text.match(/\b(?:body|saying|message)\s*[:=]?\s*["']?([\s\S]+?)["']?$/i);
|
|
162
|
+
input.body = bodyMatch ? bodyMatch[1].trim() : text;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
else if (slug === "app.gmail.fetch_emails" || slug === "app.gmail.list_messages") {
|
|
166
|
+
if (!input.query) {
|
|
167
|
+
const qMatch = text.match(/\b(?:matching|query|from|about)\s+["']?([^"'\n]+)["']?/i);
|
|
168
|
+
if (qMatch)
|
|
169
|
+
input.query = qMatch[1].trim();
|
|
170
|
+
}
|
|
171
|
+
if (input.max_results === undefined) {
|
|
172
|
+
input.max_results = extractNumberAfterKeyword(text, ["emails", "messages", "limit", "last"], 10);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
else if (slug === "app.slack.send_message" || slug === "app.slack.post_message") {
|
|
176
|
+
if (!input.channel) {
|
|
177
|
+
const chMatch = text.match(/#([a-zA-Z0-9_-]+)|\bchannel\s+([a-zA-Z0-9_-]+)/i);
|
|
178
|
+
input.channel = chMatch ? chMatch[1] || chMatch[2] : "general";
|
|
179
|
+
}
|
|
180
|
+
if (!input.text) {
|
|
181
|
+
const msgMatch = text.match(/\b(?:saying|message|text)\s*[:=]?\s*["']?([\s\S]+?)["']?$/i);
|
|
182
|
+
input.text = msgMatch ? msgMatch[1].trim() : text;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
else if (slug === "app.github.create_issue" || slug === "app.github.list_pull_requests" || slug === "app.github.list_issues") {
|
|
186
|
+
const repoMatch = text.match(/\b([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+)\b/);
|
|
187
|
+
if (repoMatch && !repoMatch[0].includes(".ai") && !repoMatch[0].includes(".com")) {
|
|
188
|
+
if (!input.owner)
|
|
189
|
+
input.owner = repoMatch[1];
|
|
190
|
+
if (!input.repo)
|
|
191
|
+
input.repo = repoMatch[2];
|
|
192
|
+
}
|
|
193
|
+
if (slug === "app.github.create_issue" && !input.title) {
|
|
194
|
+
const titleMatch = text.match(/\b(?:titled|title)\s*[:=]?\s*["']?([^"'\n]+)["']?/i);
|
|
195
|
+
input.title = titleMatch ? titleMatch[1].trim() : text.slice(0, 80);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else if (slug === "app.notion.search_pages") {
|
|
199
|
+
if (!input.query) {
|
|
200
|
+
const cleaned = text
|
|
201
|
+
.replace(/^(?:search\s+notion\s+(?:pages\s+)?(?:for)?|find\s+in\s+notion)\s*/i, "")
|
|
202
|
+
.trim();
|
|
203
|
+
input.query = cleaned || text;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
else if (slug === "instant.dns.lookup") {
|
|
207
|
+
if (!input.domain) {
|
|
208
|
+
const domainMatch = text.match(/\b((?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+(?:ai|com|io|dev|org|net|app|co|xyz))\b/i);
|
|
209
|
+
if (domainMatch) {
|
|
210
|
+
input.domain = domainMatch[1].toLowerCase();
|
|
211
|
+
}
|
|
212
|
+
else if (typeof input.hostname === "string") {
|
|
213
|
+
input.domain = input.hostname;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (!input.type) {
|
|
217
|
+
const typeMatch = text.match(/\b(AAAA|CNAME|TXT|MX|NS|A)\b/i);
|
|
218
|
+
input.type = typeMatch ? typeMatch[1].toUpperCase() : "A";
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
else if (slug === "instant.hackernews.top_stories") {
|
|
222
|
+
if (input.limit === undefined) {
|
|
223
|
+
input.limit = extractNumberAfterKeyword(text, ["top", "stories", "items", "limit", "posts"], 5);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const schemaReq = Array.isArray(tool.inputSchema?.required)
|
|
227
|
+
? tool.inputSchema.required
|
|
228
|
+
: [];
|
|
229
|
+
const missingInputs = schemaReq.filter((k) => input[k] === undefined || input[k] === null || input[k] === "");
|
|
230
|
+
return { input, missingInputs };
|
|
231
|
+
}
|
|
232
|
+
function scoreToolForPrompt(prompt, tool) {
|
|
233
|
+
const q = prompt.trim().toLowerCase();
|
|
234
|
+
if (!q)
|
|
235
|
+
return 0;
|
|
236
|
+
if (q === tool.slug.toLowerCase() || q === tool.upstreamSlug.toLowerCase()) {
|
|
237
|
+
return 1000;
|
|
238
|
+
}
|
|
239
|
+
let score = 0;
|
|
240
|
+
const slug = tool.slug.toLowerCase();
|
|
241
|
+
// High-signal intent rules
|
|
242
|
+
if (/\b(seo|meta\s+tag|open\s*graph|json-ld|jsonld|canonical|heading\s+hierarchy|audit\s+site|audit\s+page|seo\s+audit)\b/i.test(q)) {
|
|
243
|
+
if (slug === "instant.seo.audit")
|
|
244
|
+
score += 120;
|
|
245
|
+
}
|
|
246
|
+
if (/\b(crawl|spider|multi-page|sitemap|discover\s+links|all\s+pages)\b/i.test(q)) {
|
|
247
|
+
if (slug === "instant.web.crawl")
|
|
248
|
+
score += 110;
|
|
249
|
+
}
|
|
250
|
+
if (/\b(scrape|extract\s+markdown|fetch\s+page|read\s+url|read\s+webpage|scrape\s+url)\b/i.test(q)) {
|
|
251
|
+
if (slug === "instant.web.scrape")
|
|
252
|
+
score += 110;
|
|
253
|
+
}
|
|
254
|
+
const isExplicitWebSearch = /\b(search\s+(?:the\s+)?web|web\s+search|search\s+online|search\s+(?:for|about)|look\s+up|find\s+(?:news|info|information|articles)|exa|google)\b/i.test(q);
|
|
255
|
+
const hasProvisionVerb = /\b(provision|create|deploy|spin\s+up|setup|set\s+up|add\s+resource|new\s+db|new\s+database|database|db)\b/i.test(q);
|
|
256
|
+
if (isExplicitWebSearch) {
|
|
257
|
+
if (slug === "instant.web.search")
|
|
258
|
+
score += 155;
|
|
259
|
+
}
|
|
260
|
+
if (/\b(run\s+python|execute\s+code|run\s+script|sandbox|workbench|calculate|e2b)\b/i.test(q)) {
|
|
261
|
+
if (slug === "instant.code.execute" || slug === "instant.composio.workbench")
|
|
262
|
+
score += 125;
|
|
263
|
+
}
|
|
264
|
+
if (/\b(repo\s+intel|github\s+repo|inspect\s+repo|repository\s+intel|stars\s+and\s+forks)\b/i.test(q)) {
|
|
265
|
+
if (slug === "instant.github.repo_intel")
|
|
266
|
+
score += 135;
|
|
267
|
+
}
|
|
268
|
+
if (/\b(top\s*up|add\s+credits|buy\s+credits|refill\s+credits|more\s+credits|\d+\s+credits)\b/i.test(q)) {
|
|
269
|
+
if (slug === "billing.credits.topup")
|
|
270
|
+
score += 130;
|
|
271
|
+
}
|
|
272
|
+
if (/\b(upgrade\s+to\s+starter|upgrade\s+plan|starter\s+plan|starter\s+tier|upgrade\s+subscription|upgrade\s+workspace)\b/i.test(q)) {
|
|
273
|
+
if (slug === "billing.subscription.upgrade")
|
|
274
|
+
score += 130;
|
|
275
|
+
}
|
|
276
|
+
if (/\b(stripe\s+checkout|checkout\s+session|payment\s+link|billing\s+link)\b/i.test(q)) {
|
|
277
|
+
if (slug === "billing.checkout.create")
|
|
278
|
+
score += 125;
|
|
279
|
+
}
|
|
280
|
+
if (!isExplicitWebSearch || hasProvisionVerb) {
|
|
281
|
+
if (/\b(turso|libsql|sqlite\s+database|sqlite\s+db)\b/i.test(q)) {
|
|
282
|
+
if (slug.startsWith("resource.turso."))
|
|
283
|
+
score += 120;
|
|
284
|
+
if (slug.includes("database"))
|
|
285
|
+
score += 15;
|
|
286
|
+
}
|
|
287
|
+
if (/\b(neon|postgres|postgresql|pg\s+database)\b/i.test(q)) {
|
|
288
|
+
if (slug.startsWith("resource.neon."))
|
|
289
|
+
score += 120;
|
|
290
|
+
}
|
|
291
|
+
if (/\b(upstash|redis|cache\s+store|kv\s+store)\b/i.test(q)) {
|
|
292
|
+
if (slug.startsWith("resource.upstash."))
|
|
293
|
+
score += 120;
|
|
294
|
+
}
|
|
295
|
+
if (/\b(supabase)\b/i.test(q)) {
|
|
296
|
+
if (slug.startsWith("resource.supabase."))
|
|
297
|
+
score += 120;
|
|
298
|
+
}
|
|
299
|
+
if (/\b(mongodb|mongo)\b/i.test(q)) {
|
|
300
|
+
if (slug.startsWith("resource.mongodb."))
|
|
301
|
+
score += 120;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (/\b(hacker\s*news|hn\s+stories|top\s+stories)\b/i.test(q)) {
|
|
305
|
+
if (slug === "instant.hackernews.top_stories")
|
|
306
|
+
score += 125;
|
|
307
|
+
}
|
|
308
|
+
if (/\b(dns|doh|txt\s+records?|mx\s+records?|a\s+records?|cname|nameserver|lookup\s+dns|check\s+dns)\b/i.test(q)) {
|
|
309
|
+
if (slug === "instant.dns.lookup")
|
|
310
|
+
score += 135;
|
|
311
|
+
}
|
|
312
|
+
if (/\b(send\s+an?\s+email|send\s+email|email\s+to|compose\s+email)\b/i.test(q)) {
|
|
313
|
+
if (slug === "app.gmail.send_email")
|
|
314
|
+
score += 120;
|
|
315
|
+
}
|
|
316
|
+
if (/\b(list\s+emails|read\s+emails|check\s+inbox|recent\s+emails|list\s+messages|fetch\s+emails|gmail\s+inbox)\b/i.test(q)) {
|
|
317
|
+
if (slug === "app.gmail.fetch_emails" || slug === "app.gmail.list_messages")
|
|
318
|
+
score += 115;
|
|
319
|
+
}
|
|
320
|
+
if (/\b(post\s+to\s+slack|send\s+slack|slack\s+message|message\s+#)\b/i.test(q)) {
|
|
321
|
+
if (slug === "app.slack.send_message" || slug === "app.slack.post_message")
|
|
322
|
+
score += 120;
|
|
323
|
+
}
|
|
324
|
+
if (/\b(create\s+(?:a\s+)?github\s+issue|open\s+(?:a\s+)?github\s+issue|file\s+an?\s+issue)\b/i.test(q)) {
|
|
325
|
+
if (slug === "app.github.create_issue")
|
|
326
|
+
score += 120;
|
|
327
|
+
}
|
|
328
|
+
if (/\b(list\s+github\s+(?:issues|prs|pull\s+requests)|check\s+github|pull\s+requests)\b/i.test(q)) {
|
|
329
|
+
if (slug === "app.github.list_pull_requests" || slug === "app.github.list_issues")
|
|
330
|
+
score += 110;
|
|
331
|
+
}
|
|
332
|
+
if (/\b(notion\s+page|search\s+notion)\b/i.test(q)) {
|
|
333
|
+
if (slug === "app.notion.search_pages")
|
|
334
|
+
score += 115;
|
|
335
|
+
}
|
|
336
|
+
if (/\b(linear\s+issue|create\s+linear)\b/i.test(q)) {
|
|
337
|
+
if (slug === "app.linear.create_issue")
|
|
338
|
+
score += 115;
|
|
339
|
+
}
|
|
340
|
+
if (/\b(google\s+calendar|schedule\s+meeting|list\s+events|calendar\s+events)\b/i.test(q)) {
|
|
341
|
+
if (slug.includes("calendar") || slug.includes("gcal"))
|
|
342
|
+
score += 110;
|
|
343
|
+
}
|
|
344
|
+
// Generic token overlap fallback
|
|
345
|
+
const haystack = `${tool.slug} ${tool.name} ${tool.description} ${tool.providerSlug} ${(tool.categories || []).join(" ")}`.toLowerCase();
|
|
346
|
+
const tokens = q.split(/[^a-z0-9_.-]+/).filter((t) => t.length >= 2);
|
|
347
|
+
for (const token of tokens) {
|
|
348
|
+
if (slug.includes(token))
|
|
349
|
+
score += 18;
|
|
350
|
+
else if (tool.providerSlug.toLowerCase().includes(token))
|
|
351
|
+
score += 14;
|
|
352
|
+
else if (haystack.includes(token))
|
|
353
|
+
score += 6;
|
|
354
|
+
}
|
|
355
|
+
// If prompt contains a URL and no specific verb was matched, default toward scrape/audit
|
|
356
|
+
if (extractUrlFromText(prompt) && score < 40) {
|
|
357
|
+
if (slug === "instant.web.scrape")
|
|
358
|
+
score += 45;
|
|
359
|
+
if (slug === "instant.seo.audit")
|
|
360
|
+
score += 40;
|
|
361
|
+
if (slug === "instant.web.crawl")
|
|
362
|
+
score += 38;
|
|
363
|
+
}
|
|
364
|
+
return score;
|
|
365
|
+
}
|
|
366
|
+
async function tryByokLlmClassification(params) {
|
|
367
|
+
const openaiKey = params.byokKeys.OPENAI_API_KEY || "";
|
|
368
|
+
const openrouterKey = params.byokKeys.OPENROUTER_API_KEY || "";
|
|
369
|
+
const anthropicKey = params.byokKeys.ANTHROPIC_API_KEY || "";
|
|
370
|
+
if (!openaiKey && !openrouterKey && !anthropicKey) {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
const catalogBrief = params.tools
|
|
374
|
+
.slice(0, 35)
|
|
375
|
+
.map((t) => `- ${t.slug} (${t.riskLevel}, ${t.creditCost}cr): ${t.description} | schema keys: ${Object.keys(t.inputSchema?.properties || {}).join(", ")}`)
|
|
376
|
+
.join("\n");
|
|
377
|
+
const systemPrompt = [
|
|
378
|
+
"You are the Muxo Natural Language Intent Router.",
|
|
379
|
+
"Map the user's natural language request to the single best toolSlug from the catalog below and extract its JSON arguments.",
|
|
380
|
+
"Return ONLY valid JSON with shape: {\"toolSlug\": string, \"input\": object, \"reason\": string}.",
|
|
381
|
+
"Catalog:",
|
|
382
|
+
catalogBrief,
|
|
383
|
+
].join("\n");
|
|
384
|
+
try {
|
|
385
|
+
if (openaiKey || openrouterKey) {
|
|
386
|
+
const isOpenRouter = !openaiKey && Boolean(openrouterKey);
|
|
387
|
+
const endpoint = isOpenRouter
|
|
388
|
+
? "https://openrouter.ai/api/v1/chat/completions"
|
|
389
|
+
: "https://api.openai.com/v1/chat/completions";
|
|
390
|
+
const res = await fetch(endpoint, {
|
|
391
|
+
method: "POST",
|
|
392
|
+
headers: {
|
|
393
|
+
Authorization: `Bearer ${openaiKey || openrouterKey}`,
|
|
394
|
+
"Content-Type": "application/json",
|
|
395
|
+
},
|
|
396
|
+
body: JSON.stringify({
|
|
397
|
+
model: isOpenRouter ? "openai/gpt-4o-mini" : "gpt-4o-mini",
|
|
398
|
+
temperature: 0,
|
|
399
|
+
response_format: { type: "json_object" },
|
|
400
|
+
messages: [
|
|
401
|
+
{ role: "system", content: systemPrompt },
|
|
402
|
+
{ role: "user", content: params.prompt },
|
|
403
|
+
],
|
|
404
|
+
}),
|
|
405
|
+
signal: AbortSignal.timeout(4500),
|
|
406
|
+
});
|
|
407
|
+
if (res.ok) {
|
|
408
|
+
const data = (await res.json());
|
|
409
|
+
const content = data?.choices?.[0]?.message?.content;
|
|
410
|
+
if (typeof content === "string") {
|
|
411
|
+
const parsed = JSON.parse(content);
|
|
412
|
+
if (parsed && typeof parsed.toolSlug === "string") {
|
|
413
|
+
return {
|
|
414
|
+
toolSlug: parsed.toolSlug,
|
|
415
|
+
input: parsed.input && typeof parsed.input === "object"
|
|
416
|
+
? parsed.input
|
|
417
|
+
: {},
|
|
418
|
+
reason: String(parsed.reason || params.prompt),
|
|
419
|
+
providerUsed: isOpenRouter ? "openrouter" : "openai",
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
else if (anthropicKey) {
|
|
426
|
+
const res = await fetch("https://api.anthropic.com/v1/messages", {
|
|
427
|
+
method: "POST",
|
|
428
|
+
headers: {
|
|
429
|
+
"x-api-key": anthropicKey,
|
|
430
|
+
"anthropic-version": "2023-06-01",
|
|
431
|
+
"Content-Type": "application/json",
|
|
432
|
+
},
|
|
433
|
+
body: JSON.stringify({
|
|
434
|
+
model: "claude-3-5-haiku-latest",
|
|
435
|
+
max_tokens: 400,
|
|
436
|
+
system: systemPrompt,
|
|
437
|
+
messages: [{ role: "user", content: params.prompt }],
|
|
438
|
+
}),
|
|
439
|
+
signal: AbortSignal.timeout(4500),
|
|
440
|
+
});
|
|
441
|
+
if (res.ok) {
|
|
442
|
+
const data = (await res.json());
|
|
443
|
+
const text = data?.content?.[0]?.text;
|
|
444
|
+
if (typeof text === "string") {
|
|
445
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
446
|
+
if (jsonMatch) {
|
|
447
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
448
|
+
if (parsed && typeof parsed.toolSlug === "string") {
|
|
449
|
+
return {
|
|
450
|
+
toolSlug: parsed.toolSlug,
|
|
451
|
+
input: parsed.input && typeof parsed.input === "object"
|
|
452
|
+
? parsed.input
|
|
453
|
+
: {},
|
|
454
|
+
reason: String(parsed.reason || params.prompt),
|
|
455
|
+
providerUsed: "anthropic",
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
catch {
|
|
464
|
+
// Fall back to deterministic semantic router
|
|
465
|
+
}
|
|
466
|
+
return null;
|
|
467
|
+
}
|
|
468
|
+
export async function resolveNaturalLanguageIntent(params) {
|
|
469
|
+
const prompt = params.prompt.trim();
|
|
470
|
+
const tools = params.tools;
|
|
471
|
+
// 1. Check exact slug match first
|
|
472
|
+
const exactTool = tools.find((t) => t.slug.toLowerCase() === prompt.toLowerCase() ||
|
|
473
|
+
t.upstreamSlug.toLowerCase() === prompt.toLowerCase());
|
|
474
|
+
if (exactTool) {
|
|
475
|
+
const { input, missingInputs } = extractParametersFromNaturalLanguage(prompt, exactTool, params.explicitInput);
|
|
476
|
+
const needsConnection = exactTool.requiresAuth &&
|
|
477
|
+
!(params.activeConnectionProviders?.has(exactTool.providerSlug) ?? false);
|
|
478
|
+
return {
|
|
479
|
+
resolved: true,
|
|
480
|
+
tool: exactTool,
|
|
481
|
+
toolSlug: exactTool.slug,
|
|
482
|
+
input,
|
|
483
|
+
extractedInput: input,
|
|
484
|
+
reason: `Direct execution of ${exactTool.slug}`,
|
|
485
|
+
confidence: 1.0,
|
|
486
|
+
resolutionMethod: "exact_slug",
|
|
487
|
+
byokUsed: null,
|
|
488
|
+
humanSummary: `${exactTool.name} (${exactTool.slug})`,
|
|
489
|
+
missingInputs,
|
|
490
|
+
needsConnection,
|
|
491
|
+
suggestedByokProvider: suggestByokProviderForTool(exactTool),
|
|
492
|
+
alternatives: [],
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
// 2. Try BYOK LLM router if a user LLM key is configured in the workspace vault
|
|
496
|
+
if (params.preferByokLlm !== false && params.byokKeys) {
|
|
497
|
+
const llmRes = await tryByokLlmClassification({
|
|
498
|
+
prompt,
|
|
499
|
+
tools,
|
|
500
|
+
byokKeys: params.byokKeys,
|
|
501
|
+
});
|
|
502
|
+
if (llmRes) {
|
|
503
|
+
const matchedTool = tools.find((t) => t.slug.toLowerCase() === llmRes.toolSlug.toLowerCase());
|
|
504
|
+
if (matchedTool) {
|
|
505
|
+
const mergedExplicit = {
|
|
506
|
+
...llmRes.input,
|
|
507
|
+
...(params.explicitInput || {}),
|
|
508
|
+
};
|
|
509
|
+
const { input, missingInputs } = extractParametersFromNaturalLanguage(prompt, matchedTool, mergedExplicit);
|
|
510
|
+
const needsConnection = matchedTool.requiresAuth &&
|
|
511
|
+
!(params.activeConnectionProviders?.has(matchedTool.providerSlug) ?? false);
|
|
512
|
+
return {
|
|
513
|
+
resolved: true,
|
|
514
|
+
tool: matchedTool,
|
|
515
|
+
toolSlug: matchedTool.slug,
|
|
516
|
+
input,
|
|
517
|
+
extractedInput: input,
|
|
518
|
+
reason: llmRes.reason || prompt,
|
|
519
|
+
confidence: 0.96,
|
|
520
|
+
resolutionMethod: "byok_llm",
|
|
521
|
+
byokUsed: llmRes.providerUsed,
|
|
522
|
+
humanSummary: `${matchedTool.name} via ${matchedTool.slug}`,
|
|
523
|
+
missingInputs,
|
|
524
|
+
needsConnection,
|
|
525
|
+
suggestedByokProvider: suggestByokProviderForTool(matchedTool),
|
|
526
|
+
alternatives: [],
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
// 3. Deterministic Semantic + Regex Router (0 latency, works without LLM API keys)
|
|
532
|
+
const scored = tools
|
|
533
|
+
.map((tool) => ({ tool, score: scoreToolForPrompt(prompt, tool) }))
|
|
534
|
+
.filter((item) => item.score > 0)
|
|
535
|
+
.sort((a, b) => b.score - a.score);
|
|
536
|
+
const best = scored[0]?.tool || null;
|
|
537
|
+
const bestScore = scored[0]?.score || 0;
|
|
538
|
+
const alternatives = scored.slice(1, 5).map((s) => ({
|
|
539
|
+
slug: s.tool.slug,
|
|
540
|
+
name: s.tool.name,
|
|
541
|
+
riskLevel: s.tool.riskLevel,
|
|
542
|
+
creditCost: s.tool.creditCost,
|
|
543
|
+
}));
|
|
544
|
+
if (!best || bestScore < 12) {
|
|
545
|
+
return {
|
|
546
|
+
resolved: false,
|
|
547
|
+
tool: null,
|
|
548
|
+
toolSlug: null,
|
|
549
|
+
input: params.explicitInput || {},
|
|
550
|
+
extractedInput: params.explicitInput || {},
|
|
551
|
+
reason: prompt,
|
|
552
|
+
confidence: 0,
|
|
553
|
+
resolutionMethod: "semantic_router",
|
|
554
|
+
byokUsed: null,
|
|
555
|
+
humanSummary: `Could not confidently match "${prompt}" to a Muxo tool. Try specifying a tool keyword (e.g. "audit seo https://muxo.ai", "search web for AI agents", "top up 1000 credits", "create turso database").`,
|
|
556
|
+
missingInputs: [],
|
|
557
|
+
needsConnection: false,
|
|
558
|
+
suggestedByokProvider: "openai",
|
|
559
|
+
alternatives: tools.slice(0, 5).map((t) => ({
|
|
560
|
+
slug: t.slug,
|
|
561
|
+
name: t.name,
|
|
562
|
+
riskLevel: t.riskLevel,
|
|
563
|
+
creditCost: t.creditCost,
|
|
564
|
+
})),
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
const { input, missingInputs } = extractParametersFromNaturalLanguage(prompt, best, params.explicitInput);
|
|
568
|
+
const needsConnection = best.requiresAuth &&
|
|
569
|
+
!(params.activeConnectionProviders?.has(best.providerSlug) ?? false);
|
|
570
|
+
const confidence = Math.min(0.95, Math.round((bestScore / 130) * 100) / 100);
|
|
571
|
+
return {
|
|
572
|
+
resolved: true,
|
|
573
|
+
tool: best,
|
|
574
|
+
toolSlug: best.slug,
|
|
575
|
+
input,
|
|
576
|
+
extractedInput: input,
|
|
577
|
+
reason: prompt,
|
|
578
|
+
confidence,
|
|
579
|
+
resolutionMethod: "semantic_router",
|
|
580
|
+
byokUsed: null,
|
|
581
|
+
humanSummary: `${best.name} (${best.slug})`,
|
|
582
|
+
missingInputs,
|
|
583
|
+
needsConnection,
|
|
584
|
+
suggestedByokProvider: suggestByokProviderForTool(best),
|
|
585
|
+
alternatives,
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
function suggestByokProviderForTool(tool) {
|
|
589
|
+
if (tool.slug.startsWith("billing."))
|
|
590
|
+
return "stripe";
|
|
591
|
+
if (tool.lane === "composio_sdk")
|
|
592
|
+
return "composio";
|
|
593
|
+
if (tool.lane === "stripe_projects")
|
|
594
|
+
return "stripe";
|
|
595
|
+
if (tool.slug.includes("scrape") || tool.slug.includes("crawl") || tool.slug.includes("seo")) {
|
|
596
|
+
return "firecrawl";
|
|
597
|
+
}
|
|
598
|
+
if (tool.slug.includes("search"))
|
|
599
|
+
return "exa";
|
|
600
|
+
return "composio";
|
|
601
|
+
}
|
|
@@ -163,11 +163,11 @@ const INSTANT_TOOLS = [
|
|
|
163
163
|
{
|
|
164
164
|
slug: "instant.composio.workbench",
|
|
165
165
|
upstreamSlug: "COMPOSIO_REMOTE_WORKBENCH",
|
|
166
|
-
name: "
|
|
167
|
-
description: "Execute isolated data processing, JSON transformation, or Python/JS analysis in
|
|
166
|
+
name: "Instant: Remote Sandbox Workbench",
|
|
167
|
+
description: "Execute isolated data processing, JSON transformation, or Python/JS analysis in Muxo's remote sandbox workbench without OAuth.",
|
|
168
168
|
lane: "composio_instant",
|
|
169
169
|
providerSlug: "composio",
|
|
170
|
-
providerName: "
|
|
170
|
+
providerName: "Muxo Workbench",
|
|
171
171
|
categories: ["sandbox", "compute", "analysis", "instant"],
|
|
172
172
|
riskLevel: "write",
|
|
173
173
|
requiresAuth: false,
|
|
@@ -211,13 +211,14 @@ export class ComposioInstantAdapter {
|
|
|
211
211
|
}
|
|
212
212
|
async executeTool(params) {
|
|
213
213
|
const { tool, input } = params;
|
|
214
|
+
const activeComposioKey = params.composioApiKey || this.config.composioApiKey;
|
|
214
215
|
// 1. Try Composio Instant / No-Auth tool execution first if applicable
|
|
215
|
-
if (
|
|
216
|
+
if (activeComposioKey && tool.upstreamSlug.startsWith("COMPOSIO_")) {
|
|
216
217
|
try {
|
|
217
218
|
const res = await fetch(`${this.config.composioBaseUrl}/tools/execute/${encodeURIComponent(tool.upstreamSlug)}`, {
|
|
218
219
|
method: "POST",
|
|
219
220
|
headers: {
|
|
220
|
-
"x-api-key":
|
|
221
|
+
"x-api-key": activeComposioKey,
|
|
221
222
|
"Content-Type": "application/json",
|
|
222
223
|
},
|
|
223
224
|
body: JSON.stringify({
|
|
@@ -536,7 +536,8 @@ export class ComposioSdkAdapter {
|
|
|
536
536
|
},
|
|
537
537
|
};
|
|
538
538
|
};
|
|
539
|
-
|
|
539
|
+
const effectiveApiKey = params.composioApiKey || this.config.composioApiKey;
|
|
540
|
+
if (!effectiveApiKey) {
|
|
540
541
|
return buildConnectedAccountFallback();
|
|
541
542
|
}
|
|
542
543
|
const body = {
|
|
@@ -549,7 +550,10 @@ export class ComposioSdkAdapter {
|
|
|
549
550
|
try {
|
|
550
551
|
const res = await fetch(`${this.config.composioBaseUrl}/tools/execute/${encodeURIComponent(params.tool.upstreamSlug)}`, {
|
|
551
552
|
method: "POST",
|
|
552
|
-
headers:
|
|
553
|
+
headers: {
|
|
554
|
+
...this.headers,
|
|
555
|
+
"x-api-key": effectiveApiKey,
|
|
556
|
+
},
|
|
553
557
|
body: JSON.stringify(body),
|
|
554
558
|
signal: AbortSignal.timeout(25000),
|
|
555
559
|
});
|