doer-agent 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.
@@ -0,0 +1,260 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ const BEGIN_MARKER = "*** Begin Patch";
4
+ const END_MARKER = "*** End Patch";
5
+ const ADD_FILE_PREFIX = "*** Add File: ";
6
+ const DELETE_FILE_PREFIX = "*** Delete File: ";
7
+ const UPDATE_FILE_PREFIX = "*** Update File: ";
8
+ const MOVE_TO_PREFIX = "*** Move to: ";
9
+ const EOF_MARKER = "*** End of File";
10
+ function fail(message) {
11
+ throw new Error(message);
12
+ }
13
+ async function readStdin() {
14
+ const chunks = [];
15
+ for await (const chunk of process.stdin) {
16
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
17
+ }
18
+ return Buffer.concat(chunks).toString("utf8");
19
+ }
20
+ async function readPatchFromInput() {
21
+ const argv = process.argv.slice(2);
22
+ if (argv.length === 1) {
23
+ return argv[0] ?? "";
24
+ }
25
+ if (argv.length > 1) {
26
+ fail("apply_patch accepts exactly one argument or stdin.");
27
+ }
28
+ if (process.stdin.isTTY) {
29
+ fail("Usage: apply_patch 'PATCH' or echo 'PATCH' | apply_patch");
30
+ }
31
+ return await readStdin();
32
+ }
33
+ function normalizePatchText(value) {
34
+ return value.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
35
+ }
36
+ function splitPatchLines(value) {
37
+ const normalized = normalizePatchText(value);
38
+ const lines = normalized.split("\n");
39
+ while (lines.length > 0 && lines[lines.length - 1] === "") {
40
+ lines.pop();
41
+ }
42
+ return lines;
43
+ }
44
+ function validateRelativePath(filePath) {
45
+ const trimmed = filePath.trim();
46
+ if (!trimmed) {
47
+ fail("Patch path is empty.");
48
+ }
49
+ if (path.isAbsolute(trimmed)) {
50
+ fail(`Absolute paths are not allowed: ${trimmed}`);
51
+ }
52
+ const normalized = path.posix.normalize(trimmed.replace(/\\/g, "/"));
53
+ if (normalized.startsWith("../") || normalized === "..") {
54
+ fail(`Path escapes workspace: ${trimmed}`);
55
+ }
56
+ return normalized;
57
+ }
58
+ function parseUpdateChunks(lines) {
59
+ const chunks = [];
60
+ let current = [];
61
+ for (const line of lines) {
62
+ if (line === EOF_MARKER) {
63
+ continue;
64
+ }
65
+ if (line.startsWith("@@")) {
66
+ if (current.length > 0) {
67
+ chunks.push({ lines: current });
68
+ current = [];
69
+ }
70
+ continue;
71
+ }
72
+ if (!/^[ +\-]/.test(line)) {
73
+ fail(`Unsupported patch line: ${line}`);
74
+ }
75
+ current.push(line);
76
+ }
77
+ if (current.length > 0) {
78
+ chunks.push({ lines: current });
79
+ }
80
+ return chunks;
81
+ }
82
+ function parsePatch(text) {
83
+ const lines = splitPatchLines(text);
84
+ if (lines[0] !== BEGIN_MARKER) {
85
+ fail("Patch must start with '*** Begin Patch'.");
86
+ }
87
+ if (lines[lines.length - 1] !== END_MARKER) {
88
+ fail("Patch must end with '*** End Patch'.");
89
+ }
90
+ const ops = [];
91
+ let index = 1;
92
+ while (index < lines.length - 1) {
93
+ const line = lines[index] ?? "";
94
+ if (line.startsWith(ADD_FILE_PREFIX)) {
95
+ const filePath = validateRelativePath(line.slice(ADD_FILE_PREFIX.length));
96
+ index += 1;
97
+ const addLines = [];
98
+ while (index < lines.length - 1) {
99
+ const next = lines[index] ?? "";
100
+ if (next.startsWith("*** ")) {
101
+ break;
102
+ }
103
+ if (!next.startsWith("+")) {
104
+ fail(`Add file lines must start with '+': ${next}`);
105
+ }
106
+ addLines.push(next.slice(1));
107
+ index += 1;
108
+ }
109
+ ops.push({ type: "add", filePath, lines: addLines });
110
+ continue;
111
+ }
112
+ if (line.startsWith(DELETE_FILE_PREFIX)) {
113
+ const filePath = validateRelativePath(line.slice(DELETE_FILE_PREFIX.length));
114
+ index += 1;
115
+ ops.push({ type: "delete", filePath });
116
+ continue;
117
+ }
118
+ if (line.startsWith(UPDATE_FILE_PREFIX)) {
119
+ const filePath = validateRelativePath(line.slice(UPDATE_FILE_PREFIX.length));
120
+ index += 1;
121
+ let moveTo = null;
122
+ if ((lines[index] ?? "").startsWith(MOVE_TO_PREFIX)) {
123
+ moveTo = validateRelativePath((lines[index] ?? "").slice(MOVE_TO_PREFIX.length));
124
+ index += 1;
125
+ }
126
+ const updateLines = [];
127
+ while (index < lines.length - 1) {
128
+ const next = lines[index] ?? "";
129
+ if (next.startsWith(ADD_FILE_PREFIX) ||
130
+ next.startsWith(DELETE_FILE_PREFIX) ||
131
+ next.startsWith(UPDATE_FILE_PREFIX)) {
132
+ break;
133
+ }
134
+ updateLines.push(next);
135
+ index += 1;
136
+ }
137
+ ops.push({ type: "update", filePath, moveTo, chunks: parseUpdateChunks(updateLines) });
138
+ continue;
139
+ }
140
+ fail(`Unsupported patch header: ${line}`);
141
+ }
142
+ return ops;
143
+ }
144
+ function splitFileContent(content) {
145
+ const hadTrailingNewline = content.endsWith("\n");
146
+ const normalized = normalizePatchText(content);
147
+ let lines = normalized.split("\n");
148
+ if (hadTrailingNewline) {
149
+ lines = lines.slice(0, -1);
150
+ }
151
+ return { lines, hadTrailingNewline };
152
+ }
153
+ function joinFileContent(lines, trailingNewline) {
154
+ const body = lines.join("\n");
155
+ return trailingNewline && lines.length > 0 ? `${body}\n` : body;
156
+ }
157
+ function findSequence(source, needle, start) {
158
+ if (needle.length === 0) {
159
+ return start;
160
+ }
161
+ for (let i = start; i <= source.length - needle.length; i += 1) {
162
+ let matched = true;
163
+ for (let j = 0; j < needle.length; j += 1) {
164
+ if (source[i + j] !== needle[j]) {
165
+ matched = false;
166
+ break;
167
+ }
168
+ }
169
+ if (matched) {
170
+ return i;
171
+ }
172
+ }
173
+ return -1;
174
+ }
175
+ function applyChunksToLines(originalLines, chunks) {
176
+ const output = [];
177
+ let cursor = 0;
178
+ for (const chunk of chunks) {
179
+ const oldLines = [];
180
+ const newLines = [];
181
+ for (const line of chunk.lines) {
182
+ const marker = line[0];
183
+ const value = line.slice(1);
184
+ if (marker === " ") {
185
+ oldLines.push(value);
186
+ newLines.push(value);
187
+ }
188
+ else if (marker === "-") {
189
+ oldLines.push(value);
190
+ }
191
+ else if (marker === "+") {
192
+ newLines.push(value);
193
+ }
194
+ }
195
+ const matchIndex = findSequence(originalLines, oldLines, cursor);
196
+ if (matchIndex < 0) {
197
+ fail(`Failed to match patch context near: ${oldLines[0] ?? "<insert>"}`);
198
+ }
199
+ output.push(...originalLines.slice(cursor, matchIndex));
200
+ output.push(...newLines);
201
+ cursor = matchIndex + oldLines.length;
202
+ }
203
+ output.push(...originalLines.slice(cursor));
204
+ return output;
205
+ }
206
+ async function ensureParentDir(filePath) {
207
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
208
+ }
209
+ async function applyAddOp(cwd, op) {
210
+ const absPath = path.join(cwd, op.filePath);
211
+ try {
212
+ await fs.stat(absPath);
213
+ fail(`File already exists: ${op.filePath}`);
214
+ }
215
+ catch (error) {
216
+ if (error.code !== "ENOENT") {
217
+ throw error;
218
+ }
219
+ }
220
+ await ensureParentDir(absPath);
221
+ await fs.writeFile(absPath, joinFileContent(op.lines, op.lines.length > 0), "utf8");
222
+ }
223
+ async function applyDeleteOp(cwd, op) {
224
+ const absPath = path.join(cwd, op.filePath);
225
+ await fs.unlink(absPath);
226
+ }
227
+ async function applyUpdateOp(cwd, op) {
228
+ const sourcePath = path.join(cwd, op.filePath);
229
+ const sourceText = await fs.readFile(sourcePath, "utf8");
230
+ const { lines: originalLines, hadTrailingNewline } = splitFileContent(sourceText);
231
+ const nextLines = applyChunksToLines(originalLines, op.chunks);
232
+ const nextPath = path.join(cwd, op.moveTo ?? op.filePath);
233
+ await ensureParentDir(nextPath);
234
+ await fs.writeFile(nextPath, joinFileContent(nextLines, hadTrailingNewline), "utf8");
235
+ if (op.moveTo && op.moveTo !== op.filePath) {
236
+ await fs.unlink(sourcePath);
237
+ }
238
+ }
239
+ async function main() {
240
+ const patchText = await readPatchFromInput();
241
+ const ops = parsePatch(patchText);
242
+ const cwd = process.cwd();
243
+ for (const op of ops) {
244
+ if (op.type === "add") {
245
+ await applyAddOp(cwd, op);
246
+ }
247
+ else if (op.type === "delete") {
248
+ await applyDeleteOp(cwd, op);
249
+ }
250
+ else {
251
+ await applyUpdateOp(cwd, op);
252
+ }
253
+ }
254
+ process.stdout.write(`Applied patch to ${ops.length} file(s).\n`);
255
+ }
256
+ main().catch((error) => {
257
+ const message = error instanceof Error ? error.message : String(error);
258
+ process.stderr.write(`${message}\n`);
259
+ process.exitCode = 1;
260
+ });
package/dist/cli.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "./agent.js";
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, realpathSync } from "node:fs";
3
+ import { delimiter, join } from "node:path";
4
+ import { spawn } from "node:child_process";
5
+ function isWindows() {
6
+ return process.platform === "win32";
7
+ }
8
+ function candidatesFor(baseDir) {
9
+ if (isWindows()) {
10
+ return [join(baseDir, "codex.cmd"), join(baseDir, "codex.exe"), join(baseDir, "codex")];
11
+ }
12
+ return [join(baseDir, "codex")];
13
+ }
14
+ function resolveSelf() {
15
+ const selfPath = process.argv[1];
16
+ if (!selfPath)
17
+ return null;
18
+ try {
19
+ return realpathSync(selfPath);
20
+ }
21
+ catch {
22
+ return selfPath;
23
+ }
24
+ }
25
+ function findCodexBinary() {
26
+ const pathValue = process.env.PATH ?? "";
27
+ const dirs = pathValue.split(delimiter).filter(Boolean);
28
+ const self = resolveSelf();
29
+ for (const dir of dirs) {
30
+ for (const candidate of candidatesFor(dir)) {
31
+ if (!existsSync(candidate))
32
+ continue;
33
+ try {
34
+ const resolved = realpathSync(candidate);
35
+ if (self && resolved === self)
36
+ continue;
37
+ }
38
+ catch {
39
+ // no-op
40
+ }
41
+ return candidate;
42
+ }
43
+ }
44
+ return null;
45
+ }
46
+ const binary = findCodexBinary();
47
+ if (!binary) {
48
+ console.error("Unable to find a real 'codex' binary in PATH.");
49
+ console.error("Install Codex CLI first, or set PATH so another codex binary is discoverable.");
50
+ process.exit(1);
51
+ }
52
+ const child = spawn(binary, process.argv.slice(2), {
53
+ stdio: "inherit",
54
+ env: process.env,
55
+ });
56
+ child.on("error", (error) => {
57
+ console.error(`Failed to execute codex: ${error.message}`);
58
+ process.exit(1);
59
+ });
60
+ child.on("exit", (code, signal) => {
61
+ if (signal) {
62
+ process.kill(process.pid, signal);
63
+ return;
64
+ }
65
+ process.exit(code ?? 1);
66
+ });
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "./playwright-mcp-call.js";
@@ -0,0 +1,103 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { existsSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import path from "node:path";
5
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
6
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
7
+ function parseArgs(argv) {
8
+ const parsed = { tool: "", argsBase64: "" };
9
+ for (let i = 0; i < argv.length; i += 1) {
10
+ const token = argv[i];
11
+ if (token === "--tool") {
12
+ parsed.tool = (argv[i + 1] || "").trim();
13
+ i += 1;
14
+ continue;
15
+ }
16
+ if (token === "--args-base64") {
17
+ parsed.argsBase64 = (argv[i + 1] || "").trim();
18
+ i += 1;
19
+ }
20
+ }
21
+ return parsed;
22
+ }
23
+ function resolveProxyPath() {
24
+ const candidates = [
25
+ path.join(process.cwd(), "agent/runtime/bin/doer-mcp-proxy"),
26
+ path.join(process.cwd(), "runtime/bin/doer-mcp-proxy"),
27
+ ];
28
+ for (const candidate of candidates) {
29
+ if (existsSync(candidate)) {
30
+ return candidate;
31
+ }
32
+ }
33
+ return "";
34
+ }
35
+ function resolveSocketPath() {
36
+ const explicit = (process.env.DOER_MCP_SOCKET || "").trim();
37
+ if (explicit) {
38
+ return explicit;
39
+ }
40
+ const stateDir = (process.env.DOER_AGENT_STATE_DIR || "").trim() || path.join(homedir(), ".doer-agent");
41
+ return path.join(stateDir, "playwright-mcp-daemon", "playwright-mcp.sock");
42
+ }
43
+ function decodeToolArgs(argsBase64) {
44
+ if (!argsBase64) {
45
+ return {};
46
+ }
47
+ const raw = Buffer.from(argsBase64, "base64").toString("utf8");
48
+ const parsed = JSON.parse(raw);
49
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
50
+ throw new Error("decoded tool arguments must be a JSON object");
51
+ }
52
+ return parsed;
53
+ }
54
+ async function main() {
55
+ const { tool, argsBase64 } = parseArgs(process.argv.slice(2));
56
+ if (!tool) {
57
+ throw new Error("--tool is required");
58
+ }
59
+ const proxyPath = resolveProxyPath();
60
+ if (!proxyPath) {
61
+ throw new Error("doer-mcp-proxy binary not found");
62
+ }
63
+ const socketPath = resolveSocketPath();
64
+ const toolArgs = decodeToolArgs(argsBase64);
65
+ const transport = new StdioClientTransport({
66
+ command: proxyPath,
67
+ args: [],
68
+ env: {
69
+ ...process.env,
70
+ DOER_MCP_SOCKET: socketPath,
71
+ },
72
+ stderr: "pipe",
73
+ });
74
+ const client = new Client({
75
+ name: "doer-agent-playwright-mcp-runner",
76
+ version: "0.1.0",
77
+ }, {
78
+ capabilities: {},
79
+ });
80
+ try {
81
+ await client.connect(transport);
82
+ const result = await client.callTool({
83
+ name: tool,
84
+ arguments: toolArgs,
85
+ });
86
+ process.stdout.write(`${JSON.stringify({
87
+ ok: true,
88
+ tool,
89
+ content: result.content ?? null,
90
+ isError: result.isError === true,
91
+ structuredContent: result.structuredContent ?? null,
92
+ result,
93
+ })}\n`);
94
+ }
95
+ finally {
96
+ await client.close().catch(() => undefined);
97
+ }
98
+ }
99
+ main().catch((error) => {
100
+ const message = error instanceof Error ? error.message : String(error);
101
+ process.stderr.write(`${JSON.stringify({ ok: false, error: message })}\n`);
102
+ process.exit(1);
103
+ });
@@ -0,0 +1,175 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { mkdir, rm } from "node:fs/promises";
4
+ import net from "node:net";
5
+ import path from "node:path";
6
+ function parseInteger(value, fallback) {
7
+ const normalized = value?.trim();
8
+ if (!normalized) {
9
+ return fallback;
10
+ }
11
+ const parsed = Number.parseInt(normalized, 10);
12
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
13
+ }
14
+ const socketPath = process.env.DOER_PLAYWRIGHT_MCP_DAEMON_SOCKET?.trim() || "";
15
+ const targetCommand = process.env.DOER_PLAYWRIGHT_MCP_TARGET_COMMAND?.trim() || "";
16
+ const idleTtlSeconds = parseInteger(process.env.DOER_PLAYWRIGHT_MCP_DAEMON_IDLE_TTL_SECONDS, 10800);
17
+ let targetArgs = [];
18
+ try {
19
+ const parsed = JSON.parse(process.env.DOER_PLAYWRIGHT_MCP_TARGET_ARGS_JSON || "[]");
20
+ if (Array.isArray(parsed) && parsed.every((item) => typeof item === "string")) {
21
+ targetArgs = parsed;
22
+ }
23
+ }
24
+ catch {
25
+ targetArgs = [];
26
+ }
27
+ let targetEnvPatch = {};
28
+ try {
29
+ const parsed = JSON.parse(process.env.DOER_PLAYWRIGHT_MCP_TARGET_ENV_JSON || "{}");
30
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
31
+ targetEnvPatch = Object.fromEntries(Object.entries(parsed).flatMap(([key, value]) => (typeof value === "string" ? [[key, value]] : [])));
32
+ }
33
+ }
34
+ catch {
35
+ targetEnvPatch = {};
36
+ }
37
+ if (!socketPath) {
38
+ process.stderr.write("playwright-mcp-daemon error: DOER_PLAYWRIGHT_MCP_DAEMON_SOCKET is required\n");
39
+ process.exit(1);
40
+ }
41
+ if (!targetCommand) {
42
+ process.stderr.write("playwright-mcp-daemon error: DOER_PLAYWRIGHT_MCP_TARGET_COMMAND is required\n");
43
+ process.exit(1);
44
+ }
45
+ let child = null;
46
+ let activeClient = null;
47
+ let shuttingDown = false;
48
+ let lastActivityAt = Date.now();
49
+ const idleTtlMs = idleTtlSeconds * 1000;
50
+ function markActivity() {
51
+ lastActivityAt = Date.now();
52
+ }
53
+ function spawnTargetIfNeeded() {
54
+ if (child) {
55
+ return;
56
+ }
57
+ child = spawn(targetCommand, targetArgs, {
58
+ stdio: ["pipe", "pipe", "pipe"],
59
+ env: { ...process.env, ...targetEnvPatch },
60
+ });
61
+ child.stdout.on("data", (chunk) => {
62
+ markActivity();
63
+ if (activeClient && !activeClient.destroyed) {
64
+ activeClient.write(chunk);
65
+ }
66
+ });
67
+ child.stderr.on("data", (chunk) => {
68
+ process.stderr.write(chunk);
69
+ });
70
+ child.on("exit", () => {
71
+ child = null;
72
+ if (activeClient && !activeClient.destroyed) {
73
+ activeClient.end();
74
+ activeClient = null;
75
+ }
76
+ markActivity();
77
+ });
78
+ }
79
+ async function shutdown(server) {
80
+ if (shuttingDown) {
81
+ return;
82
+ }
83
+ shuttingDown = true;
84
+ if (activeClient && !activeClient.destroyed) {
85
+ activeClient.destroy();
86
+ activeClient = null;
87
+ }
88
+ if (child) {
89
+ child.kill("SIGTERM");
90
+ await new Promise((resolve) => {
91
+ const timeout = setTimeout(() => {
92
+ if (child) {
93
+ child.kill("SIGKILL");
94
+ }
95
+ resolve();
96
+ }, 2000);
97
+ timeout.unref?.();
98
+ child?.once("exit", () => {
99
+ clearTimeout(timeout);
100
+ resolve();
101
+ });
102
+ });
103
+ child = null;
104
+ }
105
+ await new Promise((resolve) => server.close(() => resolve()));
106
+ if (existsSync(socketPath)) {
107
+ await rm(socketPath, { force: true });
108
+ }
109
+ process.exit(0);
110
+ }
111
+ async function main() {
112
+ await mkdir(path.dirname(socketPath), { recursive: true });
113
+ if (existsSync(socketPath)) {
114
+ await rm(socketPath, { force: true });
115
+ }
116
+ const server = net.createServer((socket) => {
117
+ if (activeClient && !activeClient.destroyed) {
118
+ socket.end();
119
+ return;
120
+ }
121
+ activeClient = socket;
122
+ markActivity();
123
+ spawnTargetIfNeeded();
124
+ socket.on("data", (chunk) => {
125
+ markActivity();
126
+ spawnTargetIfNeeded();
127
+ if (child && !child.killed) {
128
+ child.stdin.write(chunk);
129
+ }
130
+ });
131
+ socket.on("close", () => {
132
+ if (activeClient === socket) {
133
+ activeClient = null;
134
+ }
135
+ markActivity();
136
+ });
137
+ socket.on("error", () => {
138
+ if (activeClient === socket) {
139
+ activeClient = null;
140
+ }
141
+ markActivity();
142
+ });
143
+ });
144
+ server.on("error", (error) => {
145
+ process.stderr.write(`playwright-mcp-daemon error: ${error instanceof Error ? error.message : String(error)}\n`);
146
+ process.exit(1);
147
+ });
148
+ await new Promise((resolve, reject) => {
149
+ server.once("error", reject);
150
+ server.listen(socketPath, () => {
151
+ server.off("error", reject);
152
+ resolve();
153
+ });
154
+ });
155
+ const interval = setInterval(() => {
156
+ if (shuttingDown) {
157
+ return;
158
+ }
159
+ if (activeClient && !activeClient.destroyed) {
160
+ return;
161
+ }
162
+ if (Date.now() - lastActivityAt < idleTtlMs) {
163
+ return;
164
+ }
165
+ void shutdown(server);
166
+ }, 30000);
167
+ interval.unref?.();
168
+ process.on("SIGTERM", () => {
169
+ void shutdown(server);
170
+ });
171
+ process.on("SIGINT", () => {
172
+ void shutdown(server);
173
+ });
174
+ }
175
+ void main();
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "doer-agent",
3
+ "version": "0.1.0",
4
+ "description": "Reverse-polling agent runtime for doer",
5
+ "type": "module",
6
+ "main": "dist/agent.js",
7
+ "bin": {
8
+ "doer-agent": "dist/cli.js",
9
+ "playwright-mcp-call": "dist/playwright-mcp-call-cli.js",
10
+ "codex": "dist/codex-cli.js"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "runtime",
15
+ "README.md"
16
+ ],
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "scripts": {
21
+ "build": "tsc -p tsconfig.build.json && chmod +x dist/cli.js dist/playwright-mcp-call-cli.js dist/codex-cli.js",
22
+ "start": "node --import tsx src/agent.ts",
23
+ "start:dist": "node dist/agent.js",
24
+ "codex": "codex",
25
+ "playwright-mcp-call": "node --import tsx src/playwright-mcp-call.ts",
26
+ "prepublishOnly": "npm run build"
27
+ },
28
+ "dependencies": {
29
+ "@modelcontextprotocol/sdk": "^1.27.1",
30
+ "@openai/codex-sdk": "^0.115.0",
31
+ "@playwright/mcp": "0.0.68",
32
+ "nats": "^2.29.3"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^20",
36
+ "tsx": "^4.21.0",
37
+ "typescript": "^5"
38
+ }
39
+ }
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
4
+ PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
5
+ exec node --import "$PROJECT_DIR/node_modules/tsx/dist/loader.mjs" "$PROJECT_DIR/src/apply-patch.ts" "$@"
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+
3
+ import net from "node:net";
4
+
5
+ function debugLog(...parts) {
6
+ const now = new Date().toISOString();
7
+ process.stderr.write(`[doer-mcp-proxy][${now}] ${parts.join(" ")}\n`);
8
+ }
9
+
10
+ const socketPath = (process.env.DOER_MCP_SOCKET || "").trim();
11
+
12
+ if (!socketPath) {
13
+ process.stderr.write("doer-mcp-proxy error: DOER_MCP_SOCKET is not set\n");
14
+ process.exit(1);
15
+ }
16
+
17
+ debugLog("connecting", `socket=${socketPath}`);
18
+ const socket = net.createConnection(socketPath);
19
+
20
+ socket.on("connect", () => {
21
+ debugLog("connected");
22
+ process.stdin.pipe(socket);
23
+ socket.pipe(process.stdout);
24
+ });
25
+
26
+ socket.on("error", (error) => {
27
+ const message = error instanceof Error ? error.message : String(error);
28
+ process.stderr.write(`doer-mcp-proxy error: ${message}\n`);
29
+ process.exit(1);
30
+ });
31
+
32
+ socket.on("close", () => {
33
+ debugLog("closed");
34
+ process.exit(0);
35
+ });
36
+
37
+ process.stdin.on("error", () => {
38
+ socket.destroy();
39
+ });