@anusornneal/chat-relay 0.7.2 → 0.8.1
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 +353 -320
- package/agent/capability-scheduler.mjs +121 -0
- package/agent/connection-state.mjs +141 -0
- package/agent/desktop-manager.mjs +220 -115
- package/agent/file-manager.mjs +527 -382
- package/agent/lifecycle.mjs +101 -0
- package/agent/local-agent.mjs +494 -242
- package/agent/macos-desktop-helper.js +243 -0
- package/agent/macos-desktop-runner.mjs +292 -0
- package/agent/process-manager.mjs +110 -26
- package/agent/protocol.mjs +67 -0
- package/agent/terminal-manager.mjs +587 -550
- package/agent/windows-desktop-runner.mjs +109 -0
- package/agent/windows-desktop-worker.ps1 +215 -13
- package/cli/config.mjs +114 -114
- package/cli/device-login.mjs +170 -170
- package/cli/index.mjs +241 -201
- package/cli/remote.mjs +139 -94
- package/package.json +76 -75
- package/agent/windows-desktop.ps1 +0 -288
package/cli/remote.mjs
CHANGED
|
@@ -1,94 +1,139 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import { fileURLToPath } from "node:url";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { loadConfig, saveConfig } from "./config.mjs";
|
|
5
|
-
import { login, requestJson } from "./device-login.mjs";
|
|
6
|
-
|
|
7
|
-
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
|
-
let stopping = false;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
1
|
+
import { execFile, spawn } from "node:child_process";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { loadConfig, saveConfig } from "./config.mjs";
|
|
5
|
+
import { login, requestJson } from "./device-login.mjs";
|
|
6
|
+
|
|
7
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
|
+
let stopping = false;
|
|
9
|
+
|
|
10
|
+
export function shouldRestartAgent(result, stoppingNow = false) {
|
|
11
|
+
return !stoppingNow && result?.code !== 2 && result?.code !== 3;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function protocolCompatibility(agentProtocolVersion, expectedProtocolVersion) {
|
|
15
|
+
if (agentProtocolVersion === null || agentProtocolVersion === undefined || expectedProtocolVersion === null || expectedProtocolVersion === undefined) return "unknown";
|
|
16
|
+
if (!Number.isInteger(Number(agentProtocolVersion)) || !Number.isInteger(Number(expectedProtocolVersion))) return "unknown";
|
|
17
|
+
return Number(agentProtocolVersion) === Number(expectedProtocolVersion) ? "compatible" : "incompatible";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function terminateChildTree(child) {
|
|
21
|
+
if (child.killed) return;
|
|
22
|
+
if (process.platform === "win32" && child.pid) {
|
|
23
|
+
const pid = child.pid;
|
|
24
|
+
execFile("taskkill.exe", ["/PID", String(pid), "/T"], { windowsHide: true }, () => {
|
|
25
|
+
setTimeout(() => {
|
|
26
|
+
if (child.exitCode === null && child.signalCode === null) {
|
|
27
|
+
execFile("taskkill.exe", ["/PID", String(pid), "/T", "/F"], { windowsHide: true }, () => {});
|
|
28
|
+
}
|
|
29
|
+
}, 1000);
|
|
30
|
+
});
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
child.kill("SIGTERM");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function validateConfig(config) {
|
|
37
|
+
if (!config?.relayUrl || !config?.userToken || !config?.agentToken || !config?.agentId) return false;
|
|
38
|
+
const { response } = await requestJson(`${config.relayUrl}/auth/me`, {
|
|
39
|
+
headers: { authorization: `Bearer ${config.userToken}` },
|
|
40
|
+
}).catch(() => ({ response: { ok: false } }));
|
|
41
|
+
return Boolean(response.ok);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function printSummary(config) {
|
|
45
|
+
console.log("");
|
|
46
|
+
console.log("Chat Relay Remote");
|
|
47
|
+
console.log("-----------------");
|
|
48
|
+
console.log(`Account: ${config.user?.name || config.user?.login || "signed in"}`);
|
|
49
|
+
console.log(`Relay: ${config.relayUrl}`);
|
|
50
|
+
console.log(`Agent: ${config.agentName} (${config.agentId})`);
|
|
51
|
+
console.log(`Files: ${config.allowedRoots}`);
|
|
52
|
+
console.log(`Terminal: ${config.terminalEnabled === false ? "disabled" : "enabled"}`);
|
|
53
|
+
console.log("Desktop: " + (config.desktopEnabled === true ? "enabled" : "disabled"));
|
|
54
|
+
console.log("");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function spawnAgent(config) {
|
|
58
|
+
const agentPath = path.join(packageRoot, "agent", "local-agent.mjs");
|
|
59
|
+
const cwd = String(config.allowedRoots || process.cwd()).split(";")[0] || process.cwd();
|
|
60
|
+
return spawn(process.execPath, [agentPath], {
|
|
61
|
+
cwd,
|
|
62
|
+
env: {
|
|
63
|
+
...process.env,
|
|
64
|
+
RELAY_URL: config.relayUrl,
|
|
65
|
+
AGENT_ID: config.agentId,
|
|
66
|
+
AGENT_NAME: config.agentName,
|
|
67
|
+
AGENT_TOKEN: config.agentToken,
|
|
68
|
+
TERMINAL_ENABLED: config.terminalEnabled === false ? "0" : "1",
|
|
69
|
+
DESKTOP_ENABLED: config.desktopEnabled === true ? "1" : "0",
|
|
70
|
+
ALLOWED_ROOTS: config.allowedRoots,
|
|
71
|
+
},
|
|
72
|
+
stdio: "inherit",
|
|
73
|
+
windowsHide: false,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function remote(options = {}) {
|
|
78
|
+
stopping = false;
|
|
79
|
+
let config = loadConfig();
|
|
80
|
+
if (!(await validateConfig(config))) {
|
|
81
|
+
console.log("No valid Chat Relay login found. Starting sign in...");
|
|
82
|
+
config = await login(options);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (options.allowedRoot && options.allowedRoot !== config.allowedRoots) {
|
|
86
|
+
config = { ...config, allowedRoots: options.allowedRoot };
|
|
87
|
+
saveConfig(config);
|
|
88
|
+
}
|
|
89
|
+
if (options.desktopEnabled !== undefined && Boolean(options.desktopEnabled) !== Boolean(config.desktopEnabled)) {
|
|
90
|
+
config = { ...config, desktopEnabled: Boolean(options.desktopEnabled) };
|
|
91
|
+
saveConfig(config);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
printSummary(config);
|
|
95
|
+
|
|
96
|
+
while (!stopping) {
|
|
97
|
+
const child = spawnAgent(config);
|
|
98
|
+
|
|
99
|
+
const stop = () => {
|
|
100
|
+
stopping = true;
|
|
101
|
+
terminateChildTree(child);
|
|
102
|
+
};
|
|
103
|
+
process.once("SIGINT", stop);
|
|
104
|
+
process.once("SIGTERM", stop);
|
|
105
|
+
|
|
106
|
+
const result = await new Promise((resolve) => {
|
|
107
|
+
child.once("exit", (code, signal) => resolve({ code, signal }));
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
process.removeListener("SIGINT", stop);
|
|
111
|
+
process.removeListener("SIGTERM", stop);
|
|
112
|
+
if (stopping) break;
|
|
113
|
+
|
|
114
|
+
if (result.code === 2) {
|
|
115
|
+
stopping = true;
|
|
116
|
+
console.error("Agent authorization is no longer valid. Run chat-relay login --force, then chat-relay remote.");
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
if (result.code === 3) {
|
|
120
|
+
stopping = true;
|
|
121
|
+
console.error("Agent protocol is incompatible with the relay. Update Chat Relay, then run chat-relay remote again.");
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (result.code === 4) {
|
|
126
|
+
console.log("Agent restart requested. Restarting local agent...");
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (shouldRestartAgent(result, stopping)) {
|
|
131
|
+
console.error(
|
|
132
|
+
`Agent stopped (code=${result.code ?? "null"}, signal=${result.signal ?? "none"}). Restarting in 2s...`,
|
|
133
|
+
);
|
|
134
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export { validateConfig };
|
package/package.json
CHANGED
|
@@ -1,75 +1,76 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@anusornneal/chat-relay",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Zero-checkout remote MCP agent for ChatGPT with terminal, filesystem, process, multi-user, and multi-agent support.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
"chat-relay": "bin/chat-relay.js"
|
|
8
|
-
},
|
|
9
|
-
"files": [
|
|
10
|
-
"bin/",
|
|
11
|
-
"cli/",
|
|
12
|
-
"agent/",
|
|
13
|
-
"README.md"
|
|
14
|
-
],
|
|
15
|
-
"engines": {
|
|
16
|
-
"node": ">=20"
|
|
17
|
-
},
|
|
18
|
-
"publishConfig": {
|
|
19
|
-
"access": "public"
|
|
20
|
-
},
|
|
21
|
-
"scripts": {
|
|
22
|
-
"start": "node bin/chat-relay.js remote",
|
|
23
|
-
"remote": "node bin/chat-relay.js remote",
|
|
24
|
-
"login": "node bin/chat-relay.js login",
|
|
25
|
-
"logout": "node bin/chat-relay.js logout",
|
|
26
|
-
"status": "node bin/chat-relay.js status",
|
|
27
|
-
"dev": "wrangler dev",
|
|
28
|
-
"dev:test": "wrangler dev --local --port 8790 --var AGENT_TOKEN:test-agent --var CALLER_TOKEN=test-caller --var ADMIN_TOKEN=test-admin",
|
|
29
|
-
"deploy": "wrangler deploy",
|
|
30
|
-
"agent": "node --env-file-if-exists=.dev.vars agent/local-agent.mjs",
|
|
31
|
-
"admin": "node scripts/admin.mjs",
|
|
32
|
-
"test:terminal": "node test/mcp-terminal-smoke.mjs",
|
|
33
|
-
"test:
|
|
34
|
-
"test:
|
|
35
|
-
"test:
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"test:
|
|
40
|
-
"test:
|
|
41
|
-
"test:admin-
|
|
42
|
-
"test:
|
|
43
|
-
"test:
|
|
44
|
-
"test:
|
|
45
|
-
"test:
|
|
46
|
-
"test:
|
|
47
|
-
"test:
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
"
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
"
|
|
68
|
-
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
|
|
75
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@anusornneal/chat-relay",
|
|
3
|
+
"version": "0.8.1",
|
|
4
|
+
"description": "Zero-checkout remote MCP agent for ChatGPT with terminal, filesystem, process, multi-user, and multi-agent support.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"chat-relay": "bin/chat-relay.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"cli/",
|
|
12
|
+
"agent/",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=20"
|
|
17
|
+
},
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"start": "node bin/chat-relay.js remote",
|
|
23
|
+
"remote": "node bin/chat-relay.js remote",
|
|
24
|
+
"login": "node bin/chat-relay.js login",
|
|
25
|
+
"logout": "node bin/chat-relay.js logout",
|
|
26
|
+
"status": "node bin/chat-relay.js status",
|
|
27
|
+
"dev": "wrangler dev",
|
|
28
|
+
"dev:test": "wrangler dev --local --port 8790 --var AGENT_TOKEN:test-agent --var CALLER_TOKEN=test-caller --var ADMIN_TOKEN=test-admin",
|
|
29
|
+
"deploy": "wrangler deploy",
|
|
30
|
+
"agent": "node --env-file-if-exists=.dev.vars agent/local-agent.mjs",
|
|
31
|
+
"admin": "node scripts/admin.mjs",
|
|
32
|
+
"test:terminal": "node test/mcp-terminal-smoke.mjs",
|
|
33
|
+
"test:platform": "node test/platform-smoke.mjs",
|
|
34
|
+
"test:full": "node test/multiuser-smoke.mjs",
|
|
35
|
+
"test:device-auth": "node test/device-auth-smoke.mjs",
|
|
36
|
+
"test:zero-checkout": "node test/zero-checkout-smoke.mjs",
|
|
37
|
+
"verify:publish": "npm run verify:review && node scripts/verify-publish.mjs && npx wrangler deploy --dry-run && npm audit --omit=dev",
|
|
38
|
+
"prepublishOnly": "npm run verify:publish",
|
|
39
|
+
"test:oauth": "node test/oauth-smoke.mjs",
|
|
40
|
+
"test:usage": "node test/usage-smoke.mjs",
|
|
41
|
+
"test:admin-api": "node test/admin-api-smoke.mjs",
|
|
42
|
+
"test:admin-auth": "node test/admin-auth-smoke.mjs",
|
|
43
|
+
"test:dashboard": "node test/dashboard-smoke.mjs",
|
|
44
|
+
"test:quota": "node test/quota-smoke.mjs",
|
|
45
|
+
"test:ops": "node test/ops-smoke.mjs",
|
|
46
|
+
"test:desktop": "node test/mcp-desktop-smoke.mjs",
|
|
47
|
+
"test:device-management": "node test/device-management-smoke.mjs",
|
|
48
|
+
"test:submission": "node test/submission-smoke.mjs",
|
|
49
|
+
"verify:review": "node scripts/verify-review-artifacts.mjs"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"ws": "^8.22.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@modelcontextprotocol/server": "^2.0.0",
|
|
56
|
+
"agents": "^0.24.0",
|
|
57
|
+
"wrangler": "^4.143.0",
|
|
58
|
+
"zod": "^4.6.5"
|
|
59
|
+
},
|
|
60
|
+
"repository": {
|
|
61
|
+
"type": "git",
|
|
62
|
+
"url": "git+https://github.com/anusornNeal/chat-relay.git"
|
|
63
|
+
},
|
|
64
|
+
"bugs": {
|
|
65
|
+
"url": "https://github.com/anusornNeal/chat-relay/issues"
|
|
66
|
+
},
|
|
67
|
+
"homepage": "https://github.com/anusornNeal/chat-relay#readme",
|
|
68
|
+
"keywords": [
|
|
69
|
+
"chatgpt",
|
|
70
|
+
"mcp",
|
|
71
|
+
"remote-agent",
|
|
72
|
+
"terminal",
|
|
73
|
+
"cloudflare",
|
|
74
|
+
"cli"
|
|
75
|
+
]
|
|
76
|
+
}
|