rhai-mcp 0.1.0
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/.claude-plugin/marketplace.json +11 -0
- package/.claude-plugin/plugin.json +7 -0
- package/.mcp.json +12 -0
- package/README.md +178 -0
- package/dist/agent.js +542 -0
- package/dist/agent.js.map +1 -0
- package/dist/browser.js +361 -0
- package/dist/browser.js.map +1 -0
- package/dist/cli.js +43 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.js +78 -0
- package/dist/config.js.map +1 -0
- package/dist/login.js +65 -0
- package/dist/login.js.map +1 -0
- package/dist/memory.js +266 -0
- package/dist/memory.js.map +1 -0
- package/dist/run.js +61 -0
- package/dist/run.js.map +1 -0
- package/dist/server.js +96 -0
- package/dist/server.js.map +1 -0
- package/dist/ui.js +206 -0
- package/dist/ui.js.map +1 -0
- package/package.json +34 -0
- package/prisma/schema.prisma +72 -0
package/dist/memory.js
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { packageRoot, debug } from "./config.js";
|
|
3
|
+
let prisma = null;
|
|
4
|
+
let enabled = false;
|
|
5
|
+
export function memoryEnabled() {
|
|
6
|
+
return enabled;
|
|
7
|
+
}
|
|
8
|
+
export async function initMemory() {
|
|
9
|
+
try {
|
|
10
|
+
const mod = await import("@prisma/client");
|
|
11
|
+
prisma = new mod.PrismaClient();
|
|
12
|
+
}
|
|
13
|
+
catch (err) {
|
|
14
|
+
debug("memory disabled — Prisma client not generated. Run `npx prisma generate`.", err);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
// Make sure the tables exist. Check the NEWEST table (recipe) so adding a model
|
|
18
|
+
// triggers a schema push on an older DB. db push is non-destructive (adds tables).
|
|
19
|
+
try {
|
|
20
|
+
await prisma.recipe.count();
|
|
21
|
+
enabled = true;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
try {
|
|
25
|
+
debug("initializing memory database…");
|
|
26
|
+
execSync("npx prisma db push --skip-generate", {
|
|
27
|
+
cwd: packageRoot,
|
|
28
|
+
stdio: "ignore",
|
|
29
|
+
env: process.env,
|
|
30
|
+
});
|
|
31
|
+
await prisma.recipe.count();
|
|
32
|
+
enabled = true;
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
debug("memory disabled — could not initialize database.", err);
|
|
36
|
+
enabled = false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (enabled)
|
|
40
|
+
debug("memory ready");
|
|
41
|
+
}
|
|
42
|
+
export async function closeMemory() {
|
|
43
|
+
await prisma?.$disconnect().catch(() => { });
|
|
44
|
+
}
|
|
45
|
+
export async function startTask(goal, startUrl, context, service) {
|
|
46
|
+
if (!enabled)
|
|
47
|
+
return null;
|
|
48
|
+
try {
|
|
49
|
+
const task = await prisma.task.create({
|
|
50
|
+
data: { goal, startUrl: startUrl ?? null, context: context ?? null, service: service ?? null },
|
|
51
|
+
});
|
|
52
|
+
return task.id;
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
debug("startTask failed", err);
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export async function logAction(taskId, index, tool, input, observation, ok) {
|
|
60
|
+
if (!enabled || taskId == null)
|
|
61
|
+
return;
|
|
62
|
+
try {
|
|
63
|
+
await prisma.action.create({
|
|
64
|
+
data: {
|
|
65
|
+
taskId,
|
|
66
|
+
index,
|
|
67
|
+
tool,
|
|
68
|
+
input: input ? JSON.stringify(input).slice(0, 2000) : null,
|
|
69
|
+
observation: observation.slice(0, 1000),
|
|
70
|
+
ok,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
debug("logAction failed", err);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export async function savePlan(taskId, steps, notes) {
|
|
79
|
+
if (!enabled || taskId == null)
|
|
80
|
+
return;
|
|
81
|
+
try {
|
|
82
|
+
await prisma.task.update({
|
|
83
|
+
where: { id: taskId },
|
|
84
|
+
data: { planJson: JSON.stringify({ steps, notes: notes ?? null }) },
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
debug("savePlan failed", err);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export async function finishTask(taskId, status, summary, result, steps) {
|
|
92
|
+
if (!enabled || taskId == null)
|
|
93
|
+
return;
|
|
94
|
+
try {
|
|
95
|
+
await prisma.task.update({
|
|
96
|
+
where: { id: taskId },
|
|
97
|
+
data: { status, summary, result, steps, finishedAt: new Date() },
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
debug("finishTask failed", err);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/** Upsert a distilled learning. Repeated learnings update in place and bump successCount. */
|
|
105
|
+
export async function remember(service, title, content) {
|
|
106
|
+
if (!enabled)
|
|
107
|
+
return;
|
|
108
|
+
try {
|
|
109
|
+
await prisma.memory.upsert({
|
|
110
|
+
where: { service_title: { service, title } },
|
|
111
|
+
update: { content, lastUsedAt: new Date(), successCount: { increment: 1 } },
|
|
112
|
+
create: { service, title, content },
|
|
113
|
+
});
|
|
114
|
+
debug(`remembered: [${service}] ${title}`);
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
debug("remember failed", err);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Rank stored memories by keyword overlap with the query and return the top matches.
|
|
122
|
+
* With a local SQLite DB the row count is small, so fetching all and ranking in JS
|
|
123
|
+
* is simplest and good enough.
|
|
124
|
+
*/
|
|
125
|
+
export async function recall(query, limit = 6) {
|
|
126
|
+
if (!enabled)
|
|
127
|
+
return [];
|
|
128
|
+
try {
|
|
129
|
+
const all = await prisma.memory.findMany({
|
|
130
|
+
orderBy: [{ successCount: "desc" }, { lastUsedAt: "desc" }],
|
|
131
|
+
take: 200,
|
|
132
|
+
});
|
|
133
|
+
const needles = tokenize(query);
|
|
134
|
+
if (needles.size === 0)
|
|
135
|
+
return all.slice(0, limit);
|
|
136
|
+
const scored = all
|
|
137
|
+
.map((m) => ({ m, score: overlap(`${m.service} ${m.title} ${m.content}`, needles) }))
|
|
138
|
+
.filter((x) => x.score > 0)
|
|
139
|
+
.sort((a, b) => b.score - a.score || b.m.successCount - a.m.successCount)
|
|
140
|
+
.slice(0, limit)
|
|
141
|
+
.map((x) => x.m);
|
|
142
|
+
// Touch lastUsedAt for the ones we surfaced.
|
|
143
|
+
if (scored.length) {
|
|
144
|
+
await prisma.memory
|
|
145
|
+
.updateMany({
|
|
146
|
+
where: { OR: scored.map((m) => ({ service: m.service, title: m.title })) },
|
|
147
|
+
data: { lastUsedAt: new Date() },
|
|
148
|
+
})
|
|
149
|
+
.catch(() => { });
|
|
150
|
+
}
|
|
151
|
+
return scored;
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
debug("recall failed", err);
|
|
155
|
+
return [];
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/** What happened in previous sessions — finished tasks, most recent first. */
|
|
159
|
+
export async function recentTasks(service, limit = 6) {
|
|
160
|
+
if (!enabled)
|
|
161
|
+
return [];
|
|
162
|
+
try {
|
|
163
|
+
const tasks = await prisma.task.findMany({
|
|
164
|
+
where: { status: { not: "running" }, ...(service ? { service } : {}) },
|
|
165
|
+
orderBy: { id: "desc" },
|
|
166
|
+
take: limit,
|
|
167
|
+
});
|
|
168
|
+
return tasks.map((t) => ({ goal: t.goal, status: t.status, summary: t.summary, result: t.result }));
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
debug("recentTasks failed", err);
|
|
172
|
+
return [];
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
export function formatPastTasks(tasks) {
|
|
176
|
+
if (!tasks.length)
|
|
177
|
+
return "";
|
|
178
|
+
return tasks
|
|
179
|
+
.map((t) => {
|
|
180
|
+
const detail = t.summary ? ` → ${t.summary.slice(0, 200)}` : "";
|
|
181
|
+
const res = t.result ? ` [produced: ${t.result.slice(0, 120)}]` : "";
|
|
182
|
+
return `• [${t.status}] ${t.goal}${detail}${res}`;
|
|
183
|
+
})
|
|
184
|
+
.join("\n");
|
|
185
|
+
}
|
|
186
|
+
export function formatMemories(memories) {
|
|
187
|
+
if (!memories.length)
|
|
188
|
+
return "";
|
|
189
|
+
return memories
|
|
190
|
+
.map((m) => `• [${m.service}] ${m.title} (worked ${m.successCount}×)\n ${m.content}`)
|
|
191
|
+
.join("\n");
|
|
192
|
+
}
|
|
193
|
+
function bestGoalMatch(rows, goal, threshold) {
|
|
194
|
+
const needles = tokenize(goal);
|
|
195
|
+
if (!needles.size)
|
|
196
|
+
return null;
|
|
197
|
+
let best = null;
|
|
198
|
+
let bestScore = 0;
|
|
199
|
+
for (const r of rows) {
|
|
200
|
+
const hay = tokenize(r.goal);
|
|
201
|
+
let hit = 0;
|
|
202
|
+
for (const t of needles)
|
|
203
|
+
if (hay.has(t))
|
|
204
|
+
hit++;
|
|
205
|
+
const score = hit / needles.size;
|
|
206
|
+
if (score > bestScore) {
|
|
207
|
+
bestScore = score;
|
|
208
|
+
best = r;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return bestScore >= threshold ? best : null;
|
|
212
|
+
}
|
|
213
|
+
/** Save (or update a close match of) a replayable recipe for this service+goal. */
|
|
214
|
+
export async function saveRecipe(service, goal, steps) {
|
|
215
|
+
if (!enabled || !service || steps.length === 0)
|
|
216
|
+
return;
|
|
217
|
+
try {
|
|
218
|
+
const rows = await prisma.recipe.findMany({ where: { service }, take: 50 });
|
|
219
|
+
const match = bestGoalMatch(rows, goal, 0.6);
|
|
220
|
+
if (match) {
|
|
221
|
+
await prisma.recipe.update({
|
|
222
|
+
where: { id: match.id },
|
|
223
|
+
data: { stepsJson: JSON.stringify(steps), goal, successCount: { increment: 1 }, lastUsedAt: new Date() },
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
await prisma.recipe.create({ data: { service, goal, stepsJson: JSON.stringify(steps) } });
|
|
228
|
+
}
|
|
229
|
+
debug(`saved recipe (${steps.length} steps) for ${service}`);
|
|
230
|
+
}
|
|
231
|
+
catch (err) {
|
|
232
|
+
debug("saveRecipe failed", err);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/** Find the best-matching recipe for this service+goal, or null. */
|
|
236
|
+
export async function findRecipe(service, goal) {
|
|
237
|
+
if (!enabled || !service)
|
|
238
|
+
return null;
|
|
239
|
+
try {
|
|
240
|
+
const rows = await prisma.recipe.findMany({ where: { service }, orderBy: [{ successCount: "desc" }], take: 50 });
|
|
241
|
+
const match = bestGoalMatch(rows, goal, 0.6);
|
|
242
|
+
if (!match)
|
|
243
|
+
return null;
|
|
244
|
+
await prisma.recipe.update({ where: { id: match.id }, data: { lastUsedAt: new Date() } }).catch(() => { });
|
|
245
|
+
return JSON.parse(match.stepsJson);
|
|
246
|
+
}
|
|
247
|
+
catch (err) {
|
|
248
|
+
debug("findRecipe failed", err);
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function tokenize(s) {
|
|
253
|
+
return new Set(s
|
|
254
|
+
.toLowerCase()
|
|
255
|
+
.split(/[^a-z0-9.]+/)
|
|
256
|
+
.filter((t) => t.length > 2));
|
|
257
|
+
}
|
|
258
|
+
function overlap(haystack, needles) {
|
|
259
|
+
const hay = haystack.toLowerCase();
|
|
260
|
+
let n = 0;
|
|
261
|
+
for (const t of needles)
|
|
262
|
+
if (hay.includes(t))
|
|
263
|
+
n++;
|
|
264
|
+
return n;
|
|
265
|
+
}
|
|
266
|
+
//# sourceMappingURL=memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../src/memory.ts"],"names":[],"mappings":"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"}
|
package/dist/run.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { config, setLogSink } from "./config.js";
|
|
2
|
+
import { BrowserController } from "./browser.js";
|
|
3
|
+
import { runBrowserTask } from "./agent.js";
|
|
4
|
+
import { initMemory, closeMemory } from "./memory.js";
|
|
5
|
+
import { RhaiUI } from "./ui.js";
|
|
6
|
+
/**
|
|
7
|
+
* Direct task runner for testing — bypasses MCP and runs a single task.
|
|
8
|
+
*
|
|
9
|
+
* Usage: rhai-mcp task "<goal>" [startUrl]
|
|
10
|
+
* Watch it work: RHAI_HEADLESS=false rhai-mcp task "<goal>" [startUrl]
|
|
11
|
+
* Plain logs (no TUI): RHAI_NO_UI=1 rhai-mcp task ...
|
|
12
|
+
*/
|
|
13
|
+
export async function taskMain(goal, startUrl) {
|
|
14
|
+
if (!goal) {
|
|
15
|
+
console.error('Usage: rhai-mcp task "<goal>" [startUrl]');
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
if (!config.openaiApiKey) {
|
|
19
|
+
console.error("OPENAI_API_KEY is not set (put it in .env).");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
// Extra context (e.g. a long SQL script) can be passed via env to avoid shell quoting.
|
|
23
|
+
const context = process.env.RHAI_TASK_CONTEXT || undefined;
|
|
24
|
+
// Live avatar + progress TUI when we're on a real terminal.
|
|
25
|
+
const useUI = Boolean(process.stdout.isTTY) && process.env.RHAI_NO_UI !== "1";
|
|
26
|
+
const ui = useUI ? new RhaiUI() : null;
|
|
27
|
+
if (ui) {
|
|
28
|
+
ui.start();
|
|
29
|
+
setLogSink((line) => ui.push(line));
|
|
30
|
+
}
|
|
31
|
+
await initMemory();
|
|
32
|
+
const browser = new BrowserController();
|
|
33
|
+
let result;
|
|
34
|
+
let failure = null;
|
|
35
|
+
try {
|
|
36
|
+
result = await runBrowserTask(browser, goal, { startUrl, context });
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
failure = err instanceof Error ? err : new Error(String(err));
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
await browser.close().catch(() => { });
|
|
43
|
+
await closeMemory().catch(() => { });
|
|
44
|
+
if (ui) {
|
|
45
|
+
setLogSink(null);
|
|
46
|
+
ui.stop(failure ? "error — see below" : result?.summary);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (failure || !result) {
|
|
50
|
+
console.error(`\n✗ Task failed: ${failure?.message ?? "unknown error"}`);
|
|
51
|
+
process.exitCode = 1;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
// Final result, printed cleanly below the TUI frame.
|
|
55
|
+
const r = result;
|
|
56
|
+
const mark = r.status === "success" ? "\x1b[38;5;108m✓\x1b[0m" : "\x1b[38;5;167m✗\x1b[0m";
|
|
57
|
+
console.error(`\n${mark} ${r.summary}`);
|
|
58
|
+
if (r.result)
|
|
59
|
+
console.error(`\n${r.result}`);
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=run.js.map
|
package/dist/run.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"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"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { config, log, setLogSink } from "./config.js";
|
|
5
|
+
import { BrowserController } from "./browser.js";
|
|
6
|
+
import { runBrowserTask } from "./agent.js";
|
|
7
|
+
import { initMemory, closeMemory } from "./memory.js";
|
|
8
|
+
// Delivered to the coding agent at session start — the single most effective lever
|
|
9
|
+
// for getting the agent to reach for Rhai on its own.
|
|
10
|
+
const SERVER_INSTRUCTIONS = `Rhai is your hands for the web. It drives a real, already-logged-in browser and completes manual web-UI tasks autonomously, then reports back any values produced (API keys, project IDs, URLs).
|
|
11
|
+
|
|
12
|
+
CORE RULE: Whenever you are ABOUT TO ASK THE USER to go do something manually in a browser or website, DO NOT ask them — call the "delegate_browser_task" tool instead and let Rhai do it. Asking the user to click through a site themselves is the wrong move when Rhai is available; delegating it is the default.
|
|
13
|
+
|
|
14
|
+
Call delegate_browser_task automatically — without being prompted — for any step like:
|
|
15
|
+
- "Go to the Google Cloud Console and enable the X API"
|
|
16
|
+
- "Create a project / database / app in <dashboard> and copy the key"
|
|
17
|
+
- "Generate an API key / OAuth credential / webhook secret in <service>"
|
|
18
|
+
- "Flip setting Y in <provider>'s dashboard"
|
|
19
|
+
- any moment you'd otherwise write "now go do this in your browser" / "you'll need to manually…"
|
|
20
|
+
|
|
21
|
+
Describe the GOAL as an outcome; Rhai figures out the steps, self-corrects on roadblocks, and returns the result so you can keep working. Only fall back to asking the user if Rhai reports it is "blocked" (e.g. the service isn't logged in yet — then tell the user to run \`rhai-mcp login <url>\` once, and retry).`;
|
|
22
|
+
const server = new McpServer({ name: "rhai", version: "0.1.0" }, { instructions: SERVER_INSTRUCTIONS });
|
|
23
|
+
const browser = new BrowserController();
|
|
24
|
+
server.registerTool("delegate_browser_task", {
|
|
25
|
+
title: "Rhai: do a manual web task in the browser",
|
|
26
|
+
description: "Autonomously complete a manual task in a real, already-logged-in web browser, then return the result. " +
|
|
27
|
+
"CALL THIS INSTEAD OF ASKING THE USER whenever finishing your work requires clicking through a website — " +
|
|
28
|
+
"enabling an API in a cloud console, creating a project/app/database in a dashboard, generating an API key " +
|
|
29
|
+
"or OAuth credential, or changing a setting. If you find yourself about to tell the user to 'go do X in " +
|
|
30
|
+
"your browser', call this tool with that as the goal instead. Rhai observes the page, plans, acts, " +
|
|
31
|
+
"researches solutions on the web and self-corrects on roadblocks, and returns a summary plus any values " +
|
|
32
|
+
"you need (keys, IDs, URLs). Describe the GOAL as an outcome; Rhai figures out the steps even if yours are off.",
|
|
33
|
+
inputSchema: {
|
|
34
|
+
task: z
|
|
35
|
+
.string()
|
|
36
|
+
.describe("The goal to accomplish, stated as an outcome (e.g. 'Enable the Gmail API for project my-app')."),
|
|
37
|
+
start_url: z
|
|
38
|
+
.string()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe("Optional URL to open before starting (e.g. the relevant console/dashboard page)."),
|
|
41
|
+
context: z
|
|
42
|
+
.string()
|
|
43
|
+
.optional()
|
|
44
|
+
.describe("Optional extra context: which account/project, specifics, or steps you think are involved."),
|
|
45
|
+
},
|
|
46
|
+
}, async ({ task, start_url, context }, extra) => {
|
|
47
|
+
if (!config.openaiApiKey) {
|
|
48
|
+
return {
|
|
49
|
+
isError: true,
|
|
50
|
+
content: [{ type: "text", text: "OPENAI_API_KEY is not set. Add it to the rhai environment." }],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
// Stream live progress into the coding agent's UI when the client supports it,
|
|
54
|
+
// so the user sees what Rhai is doing while the tool runs.
|
|
55
|
+
const progressToken = extra?._meta?.progressToken;
|
|
56
|
+
if (progressToken && typeof extra?.sendNotification === "function") {
|
|
57
|
+
let n = 0;
|
|
58
|
+
setLogSink((line) => {
|
|
59
|
+
const action = (line.includes("· ") ? line.split("· ").slice(1).join("· ") : line).replace(/\[\d+\]/g, "").trim();
|
|
60
|
+
n += 1;
|
|
61
|
+
void extra
|
|
62
|
+
.sendNotification({
|
|
63
|
+
method: "notifications/progress",
|
|
64
|
+
params: { progressToken, progress: n, message: `Rhai ▸ ${action}` },
|
|
65
|
+
})
|
|
66
|
+
.catch(() => { });
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const r = await runBrowserTask(browser, task, { startUrl: start_url, context });
|
|
71
|
+
const text = `Status: ${r.status} (after ${r.steps} steps)\n\n${r.summary}` +
|
|
72
|
+
(r.result ? `\n\nResult:\n${r.result}` : "");
|
|
73
|
+
return { isError: r.status === "blocked", content: [{ type: "text", text }] };
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
77
|
+
return { isError: true, content: [{ type: "text", text: `Browser task failed: ${msg}` }] };
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
setLogSink(null);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
export async function serverMain() {
|
|
84
|
+
await initMemory();
|
|
85
|
+
const shutdown = async () => {
|
|
86
|
+
await browser.close().catch(() => { });
|
|
87
|
+
await closeMemory().catch(() => { });
|
|
88
|
+
process.exit(0);
|
|
89
|
+
};
|
|
90
|
+
process.on("SIGINT", shutdown);
|
|
91
|
+
process.on("SIGTERM", shutdown);
|
|
92
|
+
const transport = new StdioServerTransport();
|
|
93
|
+
await server.connect(transport);
|
|
94
|
+
log("rhai MCP server running on stdio");
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/ui.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// Clean terminal UI for the `task` runner:
|
|
2
|
+
// • finished steps persist as a ✓ checklist that scrolls up
|
|
3
|
+
// • a small Rhai avatar + an animated status line stay pinned below
|
|
4
|
+
// In-place, cursor-relative updates (no full-screen clears) — clean in VS Code's terminal.
|
|
5
|
+
const ORANGE = "\x1b[38;5;173m";
|
|
6
|
+
const GREEN = "\x1b[38;5;108m";
|
|
7
|
+
const RED = "\x1b[38;5;167m";
|
|
8
|
+
const DIM = "\x1b[2m";
|
|
9
|
+
const BOLD = "\x1b[1m";
|
|
10
|
+
const RESET = "\x1b[0m";
|
|
11
|
+
const HIDE = "\x1b[?25l";
|
|
12
|
+
const SHOW = "\x1b[?25h";
|
|
13
|
+
const AVATAR = ["011111110", "011011110", "111111111", "011111110", "010010010"];
|
|
14
|
+
const avatarRows = AVATAR.map((r) => ORANGE + r.split("").map((c) => (c === "1" ? "█" : " ")).join("") + RESET);
|
|
15
|
+
const AVATAR_W = AVATAR[0].length;
|
|
16
|
+
const SPIN = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
17
|
+
const THINK_WORDS = ["Thinking", "Cooking", "Pondering", "Plotting", "Scheming", "Noodling"];
|
|
18
|
+
function stripAnsi(s) {
|
|
19
|
+
// eslint-disable-next-line no-control-regex
|
|
20
|
+
return s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
21
|
+
}
|
|
22
|
+
function vlen(s) {
|
|
23
|
+
return [...stripAnsi(s)].length;
|
|
24
|
+
}
|
|
25
|
+
function pad(s, w) {
|
|
26
|
+
const l = vlen(s);
|
|
27
|
+
return l > w ? s : s + " ".repeat(w - l);
|
|
28
|
+
}
|
|
29
|
+
function clip(s, w) {
|
|
30
|
+
const c = [...s];
|
|
31
|
+
return c.length > w ? c.slice(0, Math.max(0, w - 1)).join("") + "…" : s;
|
|
32
|
+
}
|
|
33
|
+
function verbFor(a) {
|
|
34
|
+
const s = a.toLowerCase();
|
|
35
|
+
if (/click/.test(s))
|
|
36
|
+
return "Clicking";
|
|
37
|
+
if (/type|press|enter/.test(s))
|
|
38
|
+
return "Typing";
|
|
39
|
+
if (/select/.test(s))
|
|
40
|
+
return "Selecting";
|
|
41
|
+
if (/open|navigat|brows/.test(s))
|
|
42
|
+
return "Browsing";
|
|
43
|
+
if (/search/.test(s))
|
|
44
|
+
return "Searching";
|
|
45
|
+
if (/look|read/.test(s))
|
|
46
|
+
return "Reading";
|
|
47
|
+
if (/wait/.test(s))
|
|
48
|
+
return "Waiting";
|
|
49
|
+
if (/plan/.test(s))
|
|
50
|
+
return "Planning";
|
|
51
|
+
if (/recall|remember|memor/.test(s))
|
|
52
|
+
return "Remembering";
|
|
53
|
+
if (/warming|launch|start/.test(s))
|
|
54
|
+
return "Warming up";
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
/** Turn raw tool-speak ("click [14]", "navigate https://…") into a clean human phrase. */
|
|
58
|
+
function prettify(a) {
|
|
59
|
+
a = a.replace(/\s*\[\d+\]/g, "").trim();
|
|
60
|
+
const nav = a.match(/^navigate\s+(\S+)/i);
|
|
61
|
+
if (nav) {
|
|
62
|
+
try {
|
|
63
|
+
return "Open " + new URL(nav[1]).host;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return "Open " + nav[1];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (/^click/i.test(a))
|
|
70
|
+
return "Click";
|
|
71
|
+
if (/^(type_keys|type)\b/i.test(a)) {
|
|
72
|
+
const m = a.match(/"([^"]*)"/);
|
|
73
|
+
return m ? `Type “${m[1].slice(0, 30)}”` : "Type text";
|
|
74
|
+
}
|
|
75
|
+
if (/^select/i.test(a))
|
|
76
|
+
return "Select option";
|
|
77
|
+
if (/^press/i.test(a))
|
|
78
|
+
return a.replace(/^press/i, "Press");
|
|
79
|
+
if (/^plan:/i.test(a))
|
|
80
|
+
return "Plan the approach";
|
|
81
|
+
if (/^web search/i.test(a)) {
|
|
82
|
+
const m = a.match(/"([^"]*)"/);
|
|
83
|
+
return m ? `Search “${m[1].slice(0, 30)}”` : "Search the web";
|
|
84
|
+
}
|
|
85
|
+
if (/^look at page/i.test(a))
|
|
86
|
+
return "Look at the page";
|
|
87
|
+
if (/^read page text/i.test(a))
|
|
88
|
+
return "Read the page";
|
|
89
|
+
if (/^wait/i.test(a))
|
|
90
|
+
return a.replace(/^wait/i, "Wait");
|
|
91
|
+
return a.charAt(0).toUpperCase() + a.slice(1);
|
|
92
|
+
}
|
|
93
|
+
export class RhaiUI {
|
|
94
|
+
current = "starting up";
|
|
95
|
+
currentFailed = false;
|
|
96
|
+
persisted = true;
|
|
97
|
+
thinking = true;
|
|
98
|
+
spin = 0;
|
|
99
|
+
think = 0;
|
|
100
|
+
height = AVATAR.length;
|
|
101
|
+
first = true;
|
|
102
|
+
timer = null;
|
|
103
|
+
active = false;
|
|
104
|
+
start() {
|
|
105
|
+
this.active = true;
|
|
106
|
+
process.stdout.write(HIDE);
|
|
107
|
+
process.stdout.write(`${BOLD}${ORANGE}RHAI${RESET}${DIM} · browser agent${RESET}\n`);
|
|
108
|
+
this.render();
|
|
109
|
+
this.timer = setInterval(() => {
|
|
110
|
+
this.spin = (this.spin + 1) % SPIN.length;
|
|
111
|
+
if (this.spin % 5 === 0)
|
|
112
|
+
this.think = (this.think + 1) % THINK_WORDS.length;
|
|
113
|
+
this.render();
|
|
114
|
+
}, 120);
|
|
115
|
+
this.timer.unref?.();
|
|
116
|
+
}
|
|
117
|
+
push(line) {
|
|
118
|
+
const clean = line.trim();
|
|
119
|
+
if (!clean || !this.active)
|
|
120
|
+
return;
|
|
121
|
+
if (/failed|error:/i.test(clean) && !clean.startsWith("▸")) {
|
|
122
|
+
this.currentFailed = true;
|
|
123
|
+
this.render();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (/^thinking/i.test(clean)) {
|
|
127
|
+
this.thinking = true;
|
|
128
|
+
this.render();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
let action = clean;
|
|
132
|
+
if (clean.startsWith("▸")) {
|
|
133
|
+
const dot = clean.indexOf("· ");
|
|
134
|
+
const bar = clean.indexOf("| ");
|
|
135
|
+
if (dot >= 0)
|
|
136
|
+
action = clean.slice(dot + 2);
|
|
137
|
+
else if (bar >= 0)
|
|
138
|
+
action = clean.slice(bar + 2);
|
|
139
|
+
else
|
|
140
|
+
action = clean.replace(/^▸\s*/, "");
|
|
141
|
+
}
|
|
142
|
+
else if (clean.startsWith("💬")) {
|
|
143
|
+
action = clean.slice(2).trim();
|
|
144
|
+
}
|
|
145
|
+
if (!this.persisted)
|
|
146
|
+
this.persistCurrent();
|
|
147
|
+
this.current = prettify(action);
|
|
148
|
+
this.currentFailed = false;
|
|
149
|
+
this.persisted = false;
|
|
150
|
+
this.thinking = false;
|
|
151
|
+
this.render();
|
|
152
|
+
}
|
|
153
|
+
stop(finalNote) {
|
|
154
|
+
if (!this.active)
|
|
155
|
+
return;
|
|
156
|
+
if (!this.persisted)
|
|
157
|
+
this.persistCurrent();
|
|
158
|
+
this.active = false;
|
|
159
|
+
if (this.timer)
|
|
160
|
+
clearInterval(this.timer);
|
|
161
|
+
const cols = process.stdout.columns || 90;
|
|
162
|
+
const leftW = Math.max(20, Math.min(cols - AVATAR_W - 4, 70));
|
|
163
|
+
process.stdout.write(`\x1b[${this.height}A`);
|
|
164
|
+
for (let i = 0; i < this.height; i++) {
|
|
165
|
+
const left = i === 0 ? `${GREEN}✓${RESET} ${BOLD}Done${RESET}${finalNote ? `${DIM} · ${clip(finalNote, leftW)}${RESET}` : ""}` : "";
|
|
166
|
+
process.stdout.write("\x1b[2K" + pad(left, leftW) + " " + avatarRows[i] + "\n");
|
|
167
|
+
}
|
|
168
|
+
process.stdout.write(SHOW);
|
|
169
|
+
}
|
|
170
|
+
persistCurrent() {
|
|
171
|
+
const cols = process.stdout.columns || 90;
|
|
172
|
+
const leftW = Math.max(20, Math.min(cols - AVATAR_W - 4, 70));
|
|
173
|
+
const mark = this.currentFailed ? `${RED}✗${RESET}` : `${GREEN}✓${RESET}`;
|
|
174
|
+
process.stdout.write(`\x1b[${this.height}A`);
|
|
175
|
+
process.stdout.write("\x1b[2K" + `${mark} ${DIM}${clip(this.current, leftW)}${RESET}` + "\n");
|
|
176
|
+
this.persisted = true;
|
|
177
|
+
this.first = true;
|
|
178
|
+
this.render();
|
|
179
|
+
}
|
|
180
|
+
render() {
|
|
181
|
+
if (!this.active && !this.first)
|
|
182
|
+
return;
|
|
183
|
+
const cols = process.stdout.columns || 90;
|
|
184
|
+
const gap = 3;
|
|
185
|
+
const leftW = Math.max(20, Math.min(cols - AVATAR_W - gap - 1, 70));
|
|
186
|
+
const spinner = `${ORANGE}${SPIN[this.spin]}${RESET}`;
|
|
187
|
+
const verb = this.thinking ? THINK_WORDS[this.think] : verbFor(this.current) ?? "Working";
|
|
188
|
+
const dots = ".".repeat((this.spin % 3) + 1);
|
|
189
|
+
const left = [
|
|
190
|
+
`${spinner} ${BOLD}${verb}${dots}${RESET}`,
|
|
191
|
+
`${DIM}↳ ${clip(this.current, leftW - 2)}${RESET}`,
|
|
192
|
+
"",
|
|
193
|
+
"",
|
|
194
|
+
"",
|
|
195
|
+
];
|
|
196
|
+
let out = "";
|
|
197
|
+
if (!this.first)
|
|
198
|
+
out += `\x1b[${this.height}A`;
|
|
199
|
+
this.first = false;
|
|
200
|
+
for (let i = 0; i < this.height; i++) {
|
|
201
|
+
out += "\x1b[2K" + pad(left[i] ?? "", leftW) + " ".repeat(gap) + avatarRows[i] + "\n";
|
|
202
|
+
}
|
|
203
|
+
process.stdout.write(out);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=ui.js.map
|