@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 CHANGED
@@ -48,10 +48,15 @@ Some clients can register the server for you, no manual JSON:
48
48
  **Claude Code:**
49
49
 
50
50
  ```bash
51
+ # this project only (default scope: local)
51
52
  claude mcp add humanjs --env HUMANJS_PERSONALITY=careful -- npx -y @humanjs/mcp
52
- # add --scope user to install it globally (all projects)
53
+
54
+ # all your projects (global): add --scope user (-s user)
55
+ claude mcp add humanjs --scope user --env HUMANJS_PERSONALITY=careful -- npx -y @humanjs/mcp
53
56
  ```
54
57
 
58
+ `--scope` is `local` (default, this project only), `user` (you, across all projects), or `project` (shared via a checked-in `.mcp.json`). Use `user` for a one-time global install.
59
+
55
60
  **Cursor** — one click:
56
61
 
57
62
  [![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/install-mcp?name=humanjs&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBodW1hbmpzL21jcCJdfQ==)
@@ -128,7 +133,7 @@ Click / rightClick / move / drag take a **selector or raw x/y coordinates** —
128
133
  | Tool | What it does |
129
134
  |---|---|
130
135
  | `human_start_recording` | Begin capturing (frames + action timeline) |
131
- | `human_stop_recording` | Finalize and write one or more files — `.mp4` / `.webm` / `.gif` / `.json` (e.g. video + timeline from one recording) |
136
+ | `human_stop_recording` | Finalize and write one or more files — `.mp4` / `.webm` (video), `.gif`, `.json` (timeline), `.ts` (HumanJS script), `.spec.ts` / `.test.ts` (Playwright test). Pass several to export multiple ways, e.g. a video + a ready-to-commit test |
132
137
 
133
138
  **Sessions** — only needed for parallel browsers; the default session is implicit:
134
139
 
package/dist/index.cjs CHANGED
@@ -192,7 +192,10 @@ var SessionManager = class {
192
192
  stop = resolve;
193
193
  });
194
194
  const video = options.video ?? true;
195
- const done = session.human.record({ video, quality: options.quality ?? "high" }, () => signal);
195
+ const done = session.human.record(
196
+ { name: options.name, video, quality: options.quality ?? "high" },
197
+ () => signal
198
+ );
196
199
  session.recording = {
197
200
  name: options.name ?? "recording",
198
201
  startedAt: Date.now(),
@@ -589,6 +592,15 @@ function resolveOutputPath(outputDir, filename) {
589
592
  }
590
593
  return path.join(outputDir, base);
591
594
  }
595
+ function resolveRecordingFormat(filename) {
596
+ const lower = filename.toLowerCase();
597
+ if (lower.endsWith(".mp4") || lower.endsWith(".webm")) return "video";
598
+ if (lower.endsWith(".gif")) return "gif";
599
+ if (lower.endsWith(".json")) return "timeline";
600
+ if (lower.endsWith(".spec.ts") || lower.endsWith(".test.ts")) return "playwright";
601
+ if (lower.endsWith(".ts")) return "humanjs";
602
+ return null;
603
+ }
592
604
 
593
605
  // src/tools/inspection.ts
594
606
  var sessionArg = zod.z.string().optional().describe("Session ID to act on. Omit to use the default session.");
@@ -955,32 +967,32 @@ function registerRecordingTools(server, { sessions, env }) {
955
967
  "human_stop_recording",
956
968
  {
957
969
  title: "Stop recording and save",
958
- 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", "demo.json"] for video + timeline. Path components are rejected for safety.`,
970
+ 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.`,
959
971
  inputSchema: {
960
972
  filenames: zod.z.array(zod.z.string()).min(1).describe(
961
- 'One or more output filenames. The recording is saved to each, format chosen by extension. e.g. ["demo.mp4"] or ["demo.mp4", "demo.gif", "demo.json"].'
973
+ '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"].'
962
974
  ),
963
975
  session: zod.z.string().optional().describe("Session ID. Omit for the default session.")
964
976
  }
965
977
  },
966
978
  async ({ filenames, session }) => {
967
- const targets = filenames.map((filename) => ({
968
- path: resolveOutputPath(env.outputDir, filename),
969
- ext: path.extname(filename).toLowerCase()
970
- }));
971
- for (const { ext } of targets) {
972
- if (ext !== ".mp4" && ext !== ".webm" && ext !== ".gif" && ext !== ".json") {
979
+ const targets = filenames.map((filename) => {
980
+ const format = resolveRecordingFormat(filename);
981
+ if (format === null) {
973
982
  throw new Error(
974
- `Unsupported output extension "${ext}". Use .mp4, .webm, .gif, or .json.`
983
+ `Unsupported output extension for "${filename}". Use .mp4/.webm (video), .gif, .json (timeline), .ts (HumanJS script), or .spec.ts/.test.ts (Playwright test).`
975
984
  );
976
985
  }
977
- }
986
+ return { path: resolveOutputPath(env.outputDir, filename), format };
987
+ });
978
988
  const recording = await sessions.stopRecording(session);
979
989
  try {
980
990
  const saved = [];
981
- for (const { path, ext } of targets) {
982
- if (ext === ".gif") saved.push(await recording.toGif(path));
983
- else if (ext === ".json") saved.push(await recording.toTimeline(path));
991
+ for (const { path, format } of targets) {
992
+ if (format === "gif") saved.push(await recording.toGif(path));
993
+ else if (format === "timeline") saved.push(await recording.toTimeline(path));
994
+ else if (format === "humanjs") saved.push(await recording.toHumanJS(path));
995
+ else if (format === "playwright") saved.push(await recording.toPlaywright(path));
984
996
  else saved.push(await recording.toVideo(path));
985
997
  }
986
998
  return { content: [{ type: "text", text: `saved recording to:
@@ -1065,6 +1077,10 @@ Recording a flow (the natural-looking way):
1065
1077
  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.
1066
1078
  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.
1067
1079
 
1080
+ 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".
1081
+
1082
+ 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.
1083
+
1068
1084
  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.
1069
1085
 
1070
1086
  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.`;