@humanjs/mcp 0.1.0 → 0.2.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/README.md +7 -2
- package/dist/index.cjs +30 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +31 -15
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
4
|
import { homedir } from 'os';
|
|
5
|
-
import { join,
|
|
5
|
+
import { join, basename, dirname } from 'path';
|
|
6
6
|
import { spawn } from 'child_process';
|
|
7
7
|
import { readFileSync } from 'fs';
|
|
8
8
|
import { createRequire } from 'module';
|
|
@@ -189,7 +189,10 @@ var SessionManager = class {
|
|
|
189
189
|
stop = resolve;
|
|
190
190
|
});
|
|
191
191
|
const video = options.video ?? true;
|
|
192
|
-
const done = session.human.record(
|
|
192
|
+
const done = session.human.record(
|
|
193
|
+
{ name: options.name, video, quality: options.quality ?? "high" },
|
|
194
|
+
() => signal
|
|
195
|
+
);
|
|
193
196
|
session.recording = {
|
|
194
197
|
name: options.name ?? "recording",
|
|
195
198
|
startedAt: Date.now(),
|
|
@@ -586,6 +589,15 @@ function resolveOutputPath(outputDir, filename) {
|
|
|
586
589
|
}
|
|
587
590
|
return join(outputDir, base);
|
|
588
591
|
}
|
|
592
|
+
function resolveRecordingFormat(filename) {
|
|
593
|
+
const lower = filename.toLowerCase();
|
|
594
|
+
if (lower.endsWith(".mp4") || lower.endsWith(".webm")) return "video";
|
|
595
|
+
if (lower.endsWith(".gif")) return "gif";
|
|
596
|
+
if (lower.endsWith(".json")) return "timeline";
|
|
597
|
+
if (lower.endsWith(".spec.ts") || lower.endsWith(".test.ts")) return "playwright";
|
|
598
|
+
if (lower.endsWith(".ts")) return "humanjs";
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
589
601
|
|
|
590
602
|
// src/tools/inspection.ts
|
|
591
603
|
var sessionArg = z.string().optional().describe("Session ID to act on. Omit to use the default session.");
|
|
@@ -952,32 +964,32 @@ function registerRecordingTools(server, { sessions, env }) {
|
|
|
952
964
|
"human_stop_recording",
|
|
953
965
|
{
|
|
954
966
|
title: "Stop recording and save",
|
|
955
|
-
description: `Stops the active recording and writes it to one or more files in HUMANJS_OUTPUT_DIR. Each filename's extension picks its format: .mp4/.webm = video, .gif = animated gif, .json = action timeline. Pass several to export the same recording multiple ways, e.g. ["demo.mp4", "
|
|
967
|
+
description: `Stops the active recording and writes it to one or more files in HUMANJS_OUTPUT_DIR. Each filename's extension picks its format: .mp4/.webm = video, .gif = animated gif, .json = action timeline, .ts = runnable HumanJS script, .spec.ts/.test.ts = @playwright/test spec (humanized, with derived assertions). Pass several to export the same recording multiple ways, e.g. ["demo.mp4", "checkout.spec.ts"] for a video plus a ready-to-commit test. Path components are rejected for safety.`,
|
|
956
968
|
inputSchema: {
|
|
957
969
|
filenames: z.array(z.string()).min(1).describe(
|
|
958
|
-
'One or more output filenames. The recording is saved to each, format chosen by extension. e.g. ["demo.mp4"] or ["demo.mp4", "demo.
|
|
970
|
+
'One or more output filenames. The recording is saved to each, format chosen by extension. e.g. ["demo.mp4"], ["checkout.spec.ts"], or ["demo.mp4", "demo.json", "demo.ts"].'
|
|
959
971
|
),
|
|
960
972
|
session: z.string().optional().describe("Session ID. Omit for the default session.")
|
|
961
973
|
}
|
|
962
974
|
},
|
|
963
975
|
async ({ filenames, session }) => {
|
|
964
|
-
const targets = filenames.map((filename) =>
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
}));
|
|
968
|
-
for (const { ext } of targets) {
|
|
969
|
-
if (ext !== ".mp4" && ext !== ".webm" && ext !== ".gif" && ext !== ".json") {
|
|
976
|
+
const targets = filenames.map((filename) => {
|
|
977
|
+
const format = resolveRecordingFormat(filename);
|
|
978
|
+
if (format === null) {
|
|
970
979
|
throw new Error(
|
|
971
|
-
`Unsupported output extension "${
|
|
980
|
+
`Unsupported output extension for "${filename}". Use .mp4/.webm (video), .gif, .json (timeline), .ts (HumanJS script), or .spec.ts/.test.ts (Playwright test).`
|
|
972
981
|
);
|
|
973
982
|
}
|
|
974
|
-
|
|
983
|
+
return { path: resolveOutputPath(env.outputDir, filename), format };
|
|
984
|
+
});
|
|
975
985
|
const recording = await sessions.stopRecording(session);
|
|
976
986
|
try {
|
|
977
987
|
const saved = [];
|
|
978
|
-
for (const { path,
|
|
979
|
-
if (
|
|
980
|
-
else if (
|
|
988
|
+
for (const { path, format } of targets) {
|
|
989
|
+
if (format === "gif") saved.push(await recording.toGif(path));
|
|
990
|
+
else if (format === "timeline") saved.push(await recording.toTimeline(path));
|
|
991
|
+
else if (format === "humanjs") saved.push(await recording.toHumanJS(path));
|
|
992
|
+
else if (format === "playwright") saved.push(await recording.toPlaywright(path));
|
|
981
993
|
else saved.push(await recording.toVideo(path));
|
|
982
994
|
}
|
|
983
995
|
return { content: [{ type: "text", text: `saved recording to:
|
|
@@ -1062,6 +1074,10 @@ Recording a flow (the natural-looking way):
|
|
|
1062
1074
|
1. EXPLORE FIRST (un-recorded). Navigate the flow once to discover correct, unambiguous selectors (human_screenshot / human_get_html / human_get_attribute). Do this by default whenever the selectors aren't already known \u2014 no need for the user to ask. Skip it only if the selectors are already known or the user tells you not to explore.
|
|
1063
1075
|
2. THEN RECORD ONE CLEAN RUN AS A SINGLE BATCH: human_start_recording + every action + human_stop_recording, all emitted in one turn. Keep selector-guessing and fumbles out of the take.
|
|
1064
1076
|
|
|
1077
|
+
Export as a test: human_stop_recording picks format by extension. A .spec.ts (or .test.ts) filename writes a ready-to-commit @playwright/test with derived assertions; a .ts writes a standalone HumanJS script; .mp4/.webm/.gif/.json are video/timeline. So "record this flow and save it as a test" = run the clean pass, then stop into e.g. "checkout.spec.ts".
|
|
1078
|
+
|
|
1079
|
+
Captured input + passwords: typed/pasted text IS recorded into the timeline and code exports, so generated scripts/tests are runnable \u2014 EXCEPT password fields, which are always masked (emitted as an empty string with a "fill in" comment). This is intentional, not a bug; don't work around it by hand-editing the secret back in. If the user explicitly wants the flow to log in, edit the exported file to read the credential from an env var (e.g. process.env.APP_PASSWORD) and tell them to set it \u2014 never hardcode a real password into a file that may be committed.
|
|
1080
|
+
|
|
1065
1081
|
Dynamic UI: prefer specific selectors (role, aria-label) over text \u2014 the same visible text often matches several cards before a filter, or the wrong one after. If a click reports multiple matches, narrow the selector.
|
|
1066
1082
|
|
|
1067
1083
|
Browser state: by default each run is a fresh, signed-out browser. If a flow needs a login, tell the user to enable persistence (human_enable_persistence or HUMANJS_PERSIST) or CDP attach \u2014 see human_browser_info.`;
|