#!/usr/bin/env node
// Claude connector, end to end: starts this Theseus checkout on a throwaway
// profile (THESEUS_USER_DATA, THESEUS_NO_UPDATE_CHECK) with the connector
// turned on in that profile, serves a local test page, and drives it over
// MCP the way Claude Code does:
//
// - type into a contenteditable editor (real insertText + Enter)
// - paste_html with a heading and a link (trusted paste through the real
// clipboard; the user's clipboard comes back afterwards)
// - upload a PNG from the allowed folder; refuse one outside it
// - refuse typing into a password field, by ref and by focus
// - no token / a browser Origin is refused; evaluate is not offered
//
// Usage: node tests/claude-connector.test.mjs [--keep] [--shot out.png]
// --keep leave Theseus running and the profile on disk afterwards
import { spawn, execFileSync } from "node:child_process";
import { createRequire } from "node:module";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import http from "node:http";
import zlib from "node:zlib";
import crypto from "node:crypto";
import { fileURLToPath } from "node:url";
const require = createRequire(import.meta.url);
const here = path.dirname(fileURLToPath(import.meta.url));
const appDir = path.resolve(here, "..");
const KEEP = process.argv.includes("--keep");
const SHOT = process.argv.includes("--shot") ? process.argv[process.argv.indexOf("--shot") + 1] : null;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
let passed = 0, failed = 0;
function check(name, cond, detail) {
if (cond) { passed++; console.log(` ok ${name}`); }
else { failed++; console.log(` FAIL ${name}${detail ? `\n ${detail}` : ""}`); }
}
// ---- fixtures --------------------------------------------------------------
function crc32(buf) {
let c, crc = 0xffffffff;
for (let n = 0; n < buf.length; n++) {
c = (crc ^ buf[n]) & 0xff;
for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
crc = (crc >>> 8) ^ c;
}
return (crc ^ 0xffffffff) >>> 0;
}
function png(w, h) {
const chunk = (type, data) => {
const len = Buffer.alloc(4); len.writeUInt32BE(data.length);
const td = Buffer.concat([Buffer.from(type), data]);
const crc = Buffer.alloc(4); crc.writeUInt32BE(crc32(td));
return Buffer.concat([len, td, crc]);
};
const ihdr = Buffer.alloc(13);
ihdr.writeUInt32BE(w, 0); ihdr.writeUInt32BE(h, 4); ihdr[8] = 8; ihdr[9] = 2;
const raw = Buffer.alloc((w * 3 + 1) * h);
for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) raw.set([217, 119, 87], y * (w * 3 + 1) + 1 + x * 3);
return Buffer.concat([Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]), chunk("IHDR", ihdr), chunk("IDAT", zlib.deflateSync(raw)), chunk("IEND", Buffer.alloc(0))]);
}
const PAGE = `