Remove HTTP server mode, keep MCP + CLI only
Drop express dependency and HTTP server in favor of MCP-only architecture. CLI still works for quick screenshots and queries. - Delete src/server.ts and src/server.test.ts - Remove express, @types/express, supertest from dependencies - Update CLI to show help when no URL provided - Remove server exports from index.ts -33 packages removed, 757 lines deleted. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Vendored
+4
-32
@@ -5,7 +5,6 @@ import { fileURLToPath } from 'node:url';
|
||||
import { parseArgs } from 'node:util';
|
||||
import { ClaudeBrowser } from './browser.js';
|
||||
import * as image from './image.js';
|
||||
import { startServer } from './server.js';
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const pkg = JSON.parse(readFileSync(resolve(__dirname, '../package.json'), 'utf-8'));
|
||||
const { values, positionals } = parseArgs({
|
||||
@@ -71,25 +70,8 @@ Image processing examples:
|
||||
browse https://example.com --resize 800x600
|
||||
browse https://example.com --compress 60
|
||||
|
||||
Server mode (default):
|
||||
browse # Start server on port 13373
|
||||
browse --headed # Start with visible browser
|
||||
|
||||
# Send commands via curl:
|
||||
curl -X POST http://localhost:13373 -d '{"cmd":"goto","url":"https://example.com"}'
|
||||
curl -X POST http://localhost:13373 -d '{"cmd":"click","selector":"button"}'
|
||||
curl -X POST http://localhost:13373 -d '{"cmd":"type","selector":"input","text":"hello"}'
|
||||
curl -X POST http://localhost:13373 -d '{"cmd":"query","selector":"a[href]"}'
|
||||
curl -X POST http://localhost:13373 -d '{"cmd":"screenshot","path":"shot.png"}'
|
||||
curl -X POST http://localhost:13373 -d '{"cmd":"url"}'
|
||||
curl -X POST http://localhost:13373 -d '{"cmd":"html"}'
|
||||
curl -X POST http://localhost:13373 -d '{"cmd":"close"}'
|
||||
|
||||
# Image processing via server:
|
||||
curl localhost:13373 -d '{"cmd":"favicon","input":"screenshot.png","outputDir":"./favicons"}'
|
||||
curl localhost:13373 -d '{"cmd":"convert","input":"img.png","output":"img.webp","format":"webp"}'
|
||||
curl localhost:13373 -d '{"cmd":"resize","input":"img.png","output":"small.png","width":400}'
|
||||
curl localhost:13373 -d '{"cmd":"compress","input":"img.png","output":"compressed.png","quality":60}'
|
||||
MCP Server (for Claude Code integration):
|
||||
browse-mcp # Run as MCP server (stdio transport)
|
||||
`;
|
||||
function getViewportConfig() {
|
||||
return {
|
||||
@@ -98,16 +80,6 @@ function getViewportConfig() {
|
||||
height: Number.parseInt(values.height),
|
||||
};
|
||||
}
|
||||
async function runServerMode() {
|
||||
const port = 13373;
|
||||
const server = await startServer({ port, ...getViewportConfig() });
|
||||
process.on('SIGINT', async () => {
|
||||
console.log('\nShutting down...');
|
||||
await server.stop();
|
||||
process.exit(0);
|
||||
});
|
||||
await new Promise(() => { });
|
||||
}
|
||||
async function processTypeActions(browser) {
|
||||
const typeActions = values.type;
|
||||
if (!typeActions?.length)
|
||||
@@ -235,8 +207,8 @@ async function main() {
|
||||
process.exit(0);
|
||||
}
|
||||
if (positionals.length === 0) {
|
||||
await runServerMode();
|
||||
return;
|
||||
console.log(HELP);
|
||||
process.exit(0);
|
||||
}
|
||||
await runBrowserMode();
|
||||
}
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
-2
@@ -1,6 +1,4 @@
|
||||
export { ClaudeBrowser } from './browser.js';
|
||||
export { BrowserServer, startServer } from './server.js';
|
||||
export { createFavicon, convert, resize, crop, compress, thumbnail, type FaviconResult, type ImageResult, type FitType, type FormatType, type ThumbnailSize, } from './image.js';
|
||||
export type { BrowserOptions, BrowserCommand, CommandResponse, ElementInfo, SuccessResponse, ErrorResponse, GotoCommand, ClickCommand, TypeCommand, QueryCommand, ScreenshotCommand, UrlCommand, HtmlCommand, BackCommand, ForwardCommand, ReloadCommand, WaitCommand, NewPageCommand, CloseCommand, EvalCommand, FaviconCommand, ConvertCommand, ResizeCommand, CropCommand, CompressCommand, ThumbnailCommand, } from './types.js';
|
||||
export type { ServerOptions } from './server.js';
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EACL,aAAa,EACb,OAAO,EACP,MAAM,EACN,IAAI,EACJ,QAAQ,EACR,SAAS,EACT,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,OAAO,EACZ,KAAK,UAAU,EACf,KAAK,aAAa,GACnB,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,cAAc,EACd,cAAc,EACd,eAAe,EACf,WAAW,EACX,eAAe,EACf,aAAa,EACb,WAAW,EACX,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,WAAW,EACX,cAAc,EACd,aAAa,EACb,WAAW,EACX,cAAc,EACd,YAAY,EACZ,WAAW,EACX,cAAc,EACd,cAAc,EACd,aAAa,EACb,WAAW,EACX,eAAe,EACf,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EACL,aAAa,EACb,OAAO,EACP,MAAM,EACN,IAAI,EACJ,QAAQ,EACR,SAAS,EACT,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,OAAO,EACZ,KAAK,UAAU,EACf,KAAK,aAAa,GACnB,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,cAAc,EACd,cAAc,EACd,eAAe,EACf,WAAW,EACX,eAAe,EACf,aAAa,EACb,WAAW,EACX,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,WAAW,EACX,cAAc,EACd,aAAa,EACb,WAAW,EACX,cAAc,EACd,YAAY,EACZ,WAAW,EACX,cAAc,EACd,cAAc,EACd,aAAa,EACb,WAAW,EACX,eAAe,EACf,gBAAgB,GACjB,MAAM,YAAY,CAAC"}
|
||||
Vendored
-1
@@ -1,4 +1,3 @@
|
||||
export { ClaudeBrowser } from './browser.js';
|
||||
export { BrowserServer, startServer } from './server.js';
|
||||
export { createFavicon, convert, resize, crop, compress, thumbnail, } from './image.js';
|
||||
//# sourceMappingURL=index.js.map
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EACL,aAAa,EACb,OAAO,EACP,MAAM,EACN,IAAI,EACJ,QAAQ,EACR,SAAS,GAMV,MAAM,YAAY,CAAC"}
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EACL,aAAa,EACb,OAAO,EACP,MAAM,EACN,IAAI,EACJ,QAAQ,EACR,SAAS,GAMV,MAAM,YAAY,CAAC"}
|
||||
Generated
+2
-376
@@ -11,23 +11,19 @@
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.26.0",
|
||||
"chalk": "^5.6.2",
|
||||
"express": "^5.2.1",
|
||||
"log-symbols": "^7.0.1",
|
||||
"playwright": "^1.41.0",
|
||||
"sharp": "^0.34.5"
|
||||
},
|
||||
"bin": {
|
||||
"browse-mcp": "dist/mcp.js",
|
||||
"claude-browse": "dist/cli.js"
|
||||
"browse": "dist/cli.js",
|
||||
"browse-mcp": "dist/mcp.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.9.0",
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/node": "^20.11.0",
|
||||
"@types/sharp": "^0.31.1",
|
||||
"@types/supertest": "^6.0.3",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
"supertest": "^7.2.2",
|
||||
"typescript": "^5.3.0",
|
||||
"vitest": "^4.0.18"
|
||||
},
|
||||
@@ -1256,29 +1252,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/hashes": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
|
||||
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^14.21.3 || >=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@paralleldrive/cuid2": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz",
|
||||
"integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "^1.1.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz",
|
||||
@@ -1636,17 +1609,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/body-parser": {
|
||||
"version": "1.19.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
|
||||
"integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/connect": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/chai": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
|
||||
@@ -1658,23 +1620,6 @@
|
||||
"assertion-error": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/connect": {
|
||||
"version": "3.4.38",
|
||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
|
||||
"integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/cookiejar": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz",
|
||||
"integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/deep-eql": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
|
||||
@@ -1689,45 +1634,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/express": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz",
|
||||
"integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/body-parser": "*",
|
||||
"@types/express-serve-static-core": "^5.0.0",
|
||||
"@types/serve-static": "^2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/express-serve-static-core": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz",
|
||||
"integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"@types/qs": "*",
|
||||
"@types/range-parser": "*",
|
||||
"@types/send": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/http-errors": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
|
||||
"integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/methods": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
|
||||
"integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.19.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz",
|
||||
@@ -1738,41 +1644,6 @@
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/qs": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
|
||||
"integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/range-parser": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
|
||||
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/send": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",
|
||||
"integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/serve-static": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz",
|
||||
"integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/http-errors": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/sharp": {
|
||||
"version": "0.31.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.31.1.tgz",
|
||||
@@ -1783,30 +1654,6 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/superagent": {
|
||||
"version": "8.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz",
|
||||
"integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/cookiejar": "^2.1.5",
|
||||
"@types/methods": "^1.1.4",
|
||||
"@types/node": "*",
|
||||
"form-data": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/supertest": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.3.tgz",
|
||||
"integrity": "sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/methods": "^1.1.4",
|
||||
"@types/superagent": "^8.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/coverage-v8": {
|
||||
"version": "4.0.18",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.18.tgz",
|
||||
@@ -1995,13 +1842,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/asap": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
||||
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/assertion-error": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
|
||||
@@ -2024,13 +1864,6 @@
|
||||
"js-tokens": "^10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
|
||||
@@ -2115,29 +1948,6 @@
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/component-emitter": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz",
|
||||
"integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
|
||||
@@ -2178,13 +1988,6 @@
|
||||
"node": ">=6.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cookiejar": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz",
|
||||
"integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cors": {
|
||||
"version": "2.8.6",
|
||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
|
||||
@@ -2233,16 +2036,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
@@ -2261,17 +2054,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/dezalgo": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
|
||||
"integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"asap": "^2.0.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
@@ -2338,22 +2120,6 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
|
||||
@@ -2519,13 +2285,6 @@
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-safe-stringify": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
|
||||
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-uri": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
|
||||
@@ -2581,64 +2340,6 @@
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data/node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data/node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/formidable": {
|
||||
"version": "3.5.4",
|
||||
"resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz",
|
||||
"integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"dezalgo": "^1.0.4",
|
||||
"once": "^1.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://ko-fi.com/tunnckoCore/commissions"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
@@ -2751,22 +2452,6 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
@@ -3030,29 +2715,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
|
||||
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.54.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
|
||||
@@ -3656,42 +3318,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/superagent": {
|
||||
"version": "10.3.0",
|
||||
"resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz",
|
||||
"integrity": "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"component-emitter": "^1.3.1",
|
||||
"cookiejar": "^2.1.4",
|
||||
"debug": "^4.3.7",
|
||||
"fast-safe-stringify": "^2.1.1",
|
||||
"form-data": "^4.0.5",
|
||||
"formidable": "^3.5.4",
|
||||
"methods": "^1.1.2",
|
||||
"mime": "2.6.0",
|
||||
"qs": "^6.14.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/supertest": {
|
||||
"version": "7.2.2",
|
||||
"resolved": "https://registry.npmjs.org/supertest/-/supertest-7.2.2.tgz",
|
||||
"integrity": "sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cookie-signature": "^1.2.2",
|
||||
"methods": "^1.1.2",
|
||||
"superagent": "^10.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
|
||||
@@ -50,19 +50,15 @@
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.26.0",
|
||||
"chalk": "^5.6.2",
|
||||
"express": "^5.2.1",
|
||||
"log-symbols": "^7.0.1",
|
||||
"playwright": "^1.41.0",
|
||||
"sharp": "^0.34.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.9.0",
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/node": "^20.11.0",
|
||||
"@types/sharp": "^0.31.1",
|
||||
"@types/supertest": "^6.0.3",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
"supertest": "^7.2.2",
|
||||
"typescript": "^5.3.0",
|
||||
"vitest": "^4.0.18"
|
||||
},
|
||||
|
||||
+4
-35
@@ -5,7 +5,6 @@ import { fileURLToPath } from 'node:url';
|
||||
import { parseArgs } from 'node:util';
|
||||
import { ClaudeBrowser } from './browser.js';
|
||||
import * as image from './image.js';
|
||||
import { startServer } from './server.js';
|
||||
import type { ElementInfo } from './types.js';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
@@ -75,25 +74,8 @@ Image processing examples:
|
||||
browse https://example.com --resize 800x600
|
||||
browse https://example.com --compress 60
|
||||
|
||||
Server mode (default):
|
||||
browse # Start server on port 13373
|
||||
browse --headed # Start with visible browser
|
||||
|
||||
# Send commands via curl:
|
||||
curl -X POST http://localhost:13373 -d '{"cmd":"goto","url":"https://example.com"}'
|
||||
curl -X POST http://localhost:13373 -d '{"cmd":"click","selector":"button"}'
|
||||
curl -X POST http://localhost:13373 -d '{"cmd":"type","selector":"input","text":"hello"}'
|
||||
curl -X POST http://localhost:13373 -d '{"cmd":"query","selector":"a[href]"}'
|
||||
curl -X POST http://localhost:13373 -d '{"cmd":"screenshot","path":"shot.png"}'
|
||||
curl -X POST http://localhost:13373 -d '{"cmd":"url"}'
|
||||
curl -X POST http://localhost:13373 -d '{"cmd":"html"}'
|
||||
curl -X POST http://localhost:13373 -d '{"cmd":"close"}'
|
||||
|
||||
# Image processing via server:
|
||||
curl localhost:13373 -d '{"cmd":"favicon","input":"screenshot.png","outputDir":"./favicons"}'
|
||||
curl localhost:13373 -d '{"cmd":"convert","input":"img.png","output":"img.webp","format":"webp"}'
|
||||
curl localhost:13373 -d '{"cmd":"resize","input":"img.png","output":"small.png","width":400}'
|
||||
curl localhost:13373 -d '{"cmd":"compress","input":"img.png","output":"compressed.png","quality":60}'
|
||||
MCP Server (for Claude Code integration):
|
||||
browse-mcp # Run as MCP server (stdio transport)
|
||||
`;
|
||||
|
||||
function getViewportConfig() {
|
||||
@@ -104,19 +86,6 @@ function getViewportConfig() {
|
||||
};
|
||||
}
|
||||
|
||||
async function runServerMode(): Promise<void> {
|
||||
const port = 13373;
|
||||
const server = await startServer({ port, ...getViewportConfig() });
|
||||
|
||||
process.on('SIGINT', async () => {
|
||||
console.log('\nShutting down...');
|
||||
await server.stop();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
await new Promise(() => {});
|
||||
}
|
||||
|
||||
async function processTypeActions(browser: ClaudeBrowser): Promise<void> {
|
||||
const typeActions = values.type as string[] | undefined;
|
||||
if (!typeActions?.length) return;
|
||||
@@ -265,8 +234,8 @@ async function main(): Promise<void> {
|
||||
}
|
||||
|
||||
if (positionals.length === 0) {
|
||||
await runServerMode();
|
||||
return;
|
||||
console.log(HELP);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
await runBrowserMode();
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export { ClaudeBrowser } from './browser.js';
|
||||
export { BrowserServer, startServer } from './server.js';
|
||||
export {
|
||||
createFavicon,
|
||||
convert,
|
||||
@@ -41,4 +40,3 @@ export type {
|
||||
CompressCommand,
|
||||
ThumbnailCommand,
|
||||
} from './types.js';
|
||||
export type { ServerOptions } from './server.js';
|
||||
|
||||
@@ -1,191 +0,0 @@
|
||||
import request from 'supertest';
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
|
||||
import { BrowserServer } from './server.js';
|
||||
|
||||
// Mock the browser module
|
||||
vi.mock('./browser.js', () => ({
|
||||
ClaudeBrowser: class MockClaudeBrowser {
|
||||
async launch() {}
|
||||
async close() {}
|
||||
async executeCommand(cmd: { cmd: string; url?: string; path?: string }) {
|
||||
switch (cmd.cmd) {
|
||||
case 'goto':
|
||||
return { ok: true, url: cmd.url, title: 'Test Page' };
|
||||
case 'click':
|
||||
return { ok: true, url: '/clicked' };
|
||||
case 'type':
|
||||
return { ok: true };
|
||||
case 'query':
|
||||
return { ok: true, count: 2, elements: [] };
|
||||
case 'url':
|
||||
return { ok: true, url: 'https://example.com', title: 'Example' };
|
||||
case 'html':
|
||||
return { ok: true, html: '<html></html>' };
|
||||
case 'back':
|
||||
return { ok: true, url: '/previous' };
|
||||
case 'forward':
|
||||
return { ok: true, url: '/next' };
|
||||
case 'reload':
|
||||
return { ok: true, url: '/current' };
|
||||
case 'wait':
|
||||
return { ok: true };
|
||||
case 'screenshot':
|
||||
return { ok: true, path: cmd.path || 'screenshot.png' };
|
||||
case 'eval':
|
||||
return { ok: true, result: 2 };
|
||||
case 'newpage':
|
||||
return { ok: true };
|
||||
case 'error':
|
||||
throw new Error('Test error');
|
||||
default:
|
||||
return { ok: true };
|
||||
}
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
describe('BrowserServer', () => {
|
||||
let server: BrowserServer;
|
||||
|
||||
beforeAll(async () => {
|
||||
server = new BrowserServer({ port: 0 }); // Use port 0 to get random available port
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('creates server with default port', () => {
|
||||
const s = new BrowserServer();
|
||||
expect(s.getPort()).toBe(13373);
|
||||
});
|
||||
|
||||
it('creates server with custom port', () => {
|
||||
const s = new BrowserServer({ port: 8080 });
|
||||
expect(s.getPort()).toBe(8080);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /', () => {
|
||||
it('handles goto command', async () => {
|
||||
const app = server.getApp();
|
||||
const res = await request(app)
|
||||
.post('/')
|
||||
.send({ cmd: 'goto', url: 'https://example.com' })
|
||||
.expect(200);
|
||||
|
||||
expect(res.body.ok).toBe(true);
|
||||
expect(res.body.url).toBe('https://example.com');
|
||||
expect(res.body.title).toBe('Test Page');
|
||||
});
|
||||
|
||||
it('handles click command', async () => {
|
||||
const app = server.getApp();
|
||||
const res = await request(app).post('/').send({ cmd: 'click', selector: '#btn' }).expect(200);
|
||||
|
||||
expect(res.body.ok).toBe(true);
|
||||
expect(res.body.url).toBe('/clicked');
|
||||
});
|
||||
|
||||
it('handles type command', async () => {
|
||||
const app = server.getApp();
|
||||
const res = await request(app)
|
||||
.post('/')
|
||||
.send({ cmd: 'type', selector: '#input', text: 'hello' })
|
||||
.expect(200);
|
||||
|
||||
expect(res.body.ok).toBe(true);
|
||||
});
|
||||
|
||||
it('handles query command', async () => {
|
||||
const app = server.getApp();
|
||||
const res = await request(app)
|
||||
.post('/')
|
||||
.send({ cmd: 'query', selector: '.items' })
|
||||
.expect(200);
|
||||
|
||||
expect(res.body.ok).toBe(true);
|
||||
expect(res.body.count).toBe(2);
|
||||
});
|
||||
|
||||
it('handles url command', async () => {
|
||||
const app = server.getApp();
|
||||
const res = await request(app).post('/').send({ cmd: 'url' }).expect(200);
|
||||
|
||||
expect(res.body.ok).toBe(true);
|
||||
expect(res.body.url).toBe('https://example.com');
|
||||
});
|
||||
|
||||
it('handles html command', async () => {
|
||||
const app = server.getApp();
|
||||
const res = await request(app).post('/').send({ cmd: 'html' }).expect(200);
|
||||
|
||||
expect(res.body.ok).toBe(true);
|
||||
expect(res.body.html).toBe('<html></html>');
|
||||
});
|
||||
|
||||
it('handles JSON string body', async () => {
|
||||
const app = server.getApp();
|
||||
const res = await request(app)
|
||||
.post('/')
|
||||
.set('Content-Type', 'text/plain')
|
||||
.send(JSON.stringify({ cmd: 'url' }))
|
||||
.expect(200);
|
||||
|
||||
expect(res.body.ok).toBe(true);
|
||||
});
|
||||
|
||||
it('handles back command', async () => {
|
||||
const app = server.getApp();
|
||||
const res = await request(app).post('/').send({ cmd: 'back' }).expect(200);
|
||||
expect(res.body.ok).toBe(true);
|
||||
});
|
||||
|
||||
it('handles forward command', async () => {
|
||||
const app = server.getApp();
|
||||
const res = await request(app).post('/').send({ cmd: 'forward' }).expect(200);
|
||||
expect(res.body.ok).toBe(true);
|
||||
});
|
||||
|
||||
it('handles reload command', async () => {
|
||||
const app = server.getApp();
|
||||
const res = await request(app).post('/').send({ cmd: 'reload' }).expect(200);
|
||||
expect(res.body.ok).toBe(true);
|
||||
});
|
||||
|
||||
it('handles wait command', async () => {
|
||||
const app = server.getApp();
|
||||
const res = await request(app).post('/').send({ cmd: 'wait', ms: 100 }).expect(200);
|
||||
expect(res.body.ok).toBe(true);
|
||||
});
|
||||
|
||||
it('handles screenshot command', async () => {
|
||||
const app = server.getApp();
|
||||
const res = await request(app)
|
||||
.post('/')
|
||||
.send({ cmd: 'screenshot', path: 'test.png' })
|
||||
.expect(200);
|
||||
expect(res.body.ok).toBe(true);
|
||||
});
|
||||
|
||||
it('handles eval command', async () => {
|
||||
const app = server.getApp();
|
||||
const res = await request(app).post('/').send({ cmd: 'eval', script: '1+1' }).expect(200);
|
||||
expect(res.body.ok).toBe(true);
|
||||
});
|
||||
|
||||
it('handles newpage command', async () => {
|
||||
const app = server.getApp();
|
||||
const res = await request(app).post('/').send({ cmd: 'newpage' }).expect(200);
|
||||
expect(res.body.ok).toBe(true);
|
||||
});
|
||||
|
||||
it('handles errors and returns 500', async () => {
|
||||
const app = server.getApp();
|
||||
const res = await request(app).post('/').send({ cmd: 'error' }).expect(500);
|
||||
expect(res.body.ok).toBe(false);
|
||||
expect(res.body.error).toBe('Test error');
|
||||
});
|
||||
});
|
||||
});
|
||||
-111
@@ -1,111 +0,0 @@
|
||||
import chalk from 'chalk';
|
||||
import express, { type Request, type Response } from 'express';
|
||||
import logSymbols from 'log-symbols';
|
||||
import { ClaudeBrowser } from './browser.js';
|
||||
import { logger, ts } from './logger.js';
|
||||
import type { BrowserCommand, BrowserOptions } from './types.js';
|
||||
|
||||
export interface ServerOptions extends BrowserOptions {
|
||||
port?: number;
|
||||
}
|
||||
|
||||
function printBanner(port: number): void {
|
||||
console.log();
|
||||
console.log(chalk.cyan.bold(' 🌐 Claude Browse Server'));
|
||||
console.log(chalk.dim(' ─────────────────────────'));
|
||||
console.log(` ${logSymbols.success} Listening on ${chalk.bold(`http://localhost:${port}`)}`);
|
||||
console.log();
|
||||
console.log(chalk.dim(' Commands:'));
|
||||
console.log(
|
||||
` ${chalk.cyan('goto')} ${chalk.yellow('click')} ${chalk.magenta('type')} ${chalk.blue('query')} ${chalk.green('screenshot')}`
|
||||
);
|
||||
console.log(
|
||||
` ${chalk.cyan('url')} ${chalk.blue('html')} ${chalk.yellow('back')} ${chalk.yellow('forward')} ${chalk.yellow('reload')} ${chalk.gray('wait')} ${chalk.red('close')}`
|
||||
);
|
||||
console.log();
|
||||
console.log(chalk.dim(' Example:'));
|
||||
console.log(
|
||||
chalk.gray(` curl -X POST localhost:${port} -d '{"cmd":"goto","url":"https://example.com"}'`)
|
||||
);
|
||||
console.log();
|
||||
}
|
||||
|
||||
export class BrowserServer {
|
||||
private browser: ClaudeBrowser;
|
||||
private app = express();
|
||||
private server: ReturnType<typeof this.app.listen> | null = null;
|
||||
private port: number;
|
||||
|
||||
constructor(options: ServerOptions = {}) {
|
||||
this.browser = new ClaudeBrowser(options);
|
||||
this.port = options.port ?? 13373;
|
||||
this.setupMiddleware();
|
||||
this.setupRoutes();
|
||||
}
|
||||
|
||||
private setupMiddleware(): void {
|
||||
this.app.use(express.json());
|
||||
this.app.use(express.text({ type: '*/*' }));
|
||||
}
|
||||
|
||||
private setupRoutes(): void {
|
||||
this.app.post('/', (req, res) => this.handleCommand(req, res));
|
||||
}
|
||||
|
||||
private async handleCommand(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const cmd: BrowserCommand = typeof req.body === 'string' ? JSON.parse(req.body) : req.body;
|
||||
logger.command(cmd);
|
||||
|
||||
if (cmd.cmd === 'close') {
|
||||
logger.result(cmd, { ok: true });
|
||||
res.json({ ok: true });
|
||||
await this.stop();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const result = await this.browser.executeCommand(cmd);
|
||||
logger.result(cmd, result);
|
||||
res.json(result);
|
||||
} catch (err) {
|
||||
const error = (err as Error).message;
|
||||
console.log(`${ts()} ${logSymbols.error} ${chalk.red(error)}`);
|
||||
res.status(500).json({ ok: false, error });
|
||||
}
|
||||
}
|
||||
|
||||
async start(): Promise<void> {
|
||||
await this.browser.launch();
|
||||
|
||||
return new Promise((resolve) => {
|
||||
this.server = this.app.listen(this.port, () => {
|
||||
printBanner(this.port);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
console.log(chalk.dim('\n Shutting down...'));
|
||||
if (this.server) {
|
||||
this.server.close();
|
||||
this.server = null;
|
||||
}
|
||||
await this.browser.close();
|
||||
console.log(` ${logSymbols.success} Browser closed\n`);
|
||||
}
|
||||
|
||||
getPort(): number {
|
||||
return this.port;
|
||||
}
|
||||
|
||||
getApp() {
|
||||
return this.app;
|
||||
}
|
||||
}
|
||||
|
||||
export async function startServer(options: ServerOptions = {}): Promise<BrowserServer> {
|
||||
const server = new BrowserServer(options);
|
||||
await server.start();
|
||||
return server;
|
||||
}
|
||||
Reference in New Issue
Block a user