@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/index.mjs
CHANGED
|
@@ -1,201 +1,241 @@
|
|
|
1
|
-
import { clearConfig, configPath, loadConfig, saveConfig } from "./config.mjs";
|
|
2
|
-
import { login, requestJson } from "./device-login.mjs";
|
|
3
|
-
import { remote, validateConfig } from "./remote.mjs";
|
|
4
|
-
|
|
5
|
-
function parseOptions(args) {
|
|
6
|
-
const options = {};
|
|
7
|
-
const rest = [];
|
|
8
|
-
for (let index = 0; index < args.length; index++) {
|
|
9
|
-
const arg = args[index];
|
|
10
|
-
if (arg === "--no-open") options.noOpen = true;
|
|
11
|
-
else if (arg === "--force") options.force = true;
|
|
12
|
-
else if (arg === "--desktop") options.desktopEnabled = true;
|
|
13
|
-
else if (arg === "--no-desktop") options.desktopEnabled = false;
|
|
14
|
-
else if (arg === "--relay") options.relayUrl = args[++index];
|
|
15
|
-
else if (arg.startsWith("--relay=")) options.relayUrl = arg.slice(8);
|
|
16
|
-
else if (arg === "--root") options.allowedRoot = args[++index];
|
|
17
|
-
else if (arg.startsWith("--root=")) options.allowedRoot = arg.slice(7);
|
|
18
|
-
else if (arg === "--name") options.agentName = args[++index];
|
|
19
|
-
else if (arg.startsWith("--name=")) options.agentName = arg.slice(7);
|
|
20
|
-
else if (arg === "--agent-id") options.agentId = args[++index];
|
|
21
|
-
else if (arg.startsWith("--agent-id=")) options.agentId = arg.slice(11);
|
|
22
|
-
else rest.push(arg);
|
|
23
|
-
}
|
|
24
|
-
return { options, rest };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function help() {
|
|
28
|
-
console.log(`Chat Relay
|
|
29
|
-
|
|
30
|
-
Usage:
|
|
31
|
-
chat-relay remote [options] Connect this computer to ChatGPT
|
|
32
|
-
chat-relay login [options] Sign in and register this computer
|
|
33
|
-
chat-relay status Show account and connection status
|
|
34
|
-
chat-relay
|
|
35
|
-
chat-relay
|
|
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
|
-
console.log("
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}))
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
console.log("
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
console.log("Access:
|
|
81
|
-
console.log(
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
console.log("
|
|
117
|
-
console.log(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
console.log(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (
|
|
145
|
-
console.log("
|
|
146
|
-
return
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
1
|
+
import { clearConfig, configPath, loadConfig, saveConfig } from "./config.mjs";
|
|
2
|
+
import { login, requestJson } from "./device-login.mjs";
|
|
3
|
+
import { protocolCompatibility, remote, validateConfig } from "./remote.mjs";
|
|
4
|
+
|
|
5
|
+
function parseOptions(args) {
|
|
6
|
+
const options = {};
|
|
7
|
+
const rest = [];
|
|
8
|
+
for (let index = 0; index < args.length; index++) {
|
|
9
|
+
const arg = args[index];
|
|
10
|
+
if (arg === "--no-open") options.noOpen = true;
|
|
11
|
+
else if (arg === "--force") options.force = true;
|
|
12
|
+
else if (arg === "--desktop") options.desktopEnabled = true;
|
|
13
|
+
else if (arg === "--no-desktop") options.desktopEnabled = false;
|
|
14
|
+
else if (arg === "--relay") options.relayUrl = args[++index];
|
|
15
|
+
else if (arg.startsWith("--relay=")) options.relayUrl = arg.slice(8);
|
|
16
|
+
else if (arg === "--root") options.allowedRoot = args[++index];
|
|
17
|
+
else if (arg.startsWith("--root=")) options.allowedRoot = arg.slice(7);
|
|
18
|
+
else if (arg === "--name") options.agentName = args[++index];
|
|
19
|
+
else if (arg.startsWith("--name=")) options.agentName = arg.slice(7);
|
|
20
|
+
else if (arg === "--agent-id") options.agentId = args[++index];
|
|
21
|
+
else if (arg.startsWith("--agent-id=")) options.agentId = arg.slice(11);
|
|
22
|
+
else rest.push(arg);
|
|
23
|
+
}
|
|
24
|
+
return { options, rest };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function help() {
|
|
28
|
+
console.log(`Chat Relay
|
|
29
|
+
|
|
30
|
+
Usage:
|
|
31
|
+
chat-relay remote [options] Connect this computer to ChatGPT
|
|
32
|
+
chat-relay login [options] Sign in and register this computer
|
|
33
|
+
chat-relay status Show account and connection status
|
|
34
|
+
chat-relay drain Stop accepting new long-running work
|
|
35
|
+
chat-relay resume Resume normal work admission
|
|
36
|
+
chat-relay restart Restart after drain completes
|
|
37
|
+
chat-relay logout Revoke this computer login
|
|
38
|
+
chat-relay help Show this help
|
|
39
|
+
|
|
40
|
+
Options:
|
|
41
|
+
--relay <url> Relay Worker URL
|
|
42
|
+
--root <path> Allowed filesystem root (semicolon-separated for multiple)
|
|
43
|
+
--name <name> Computer display name
|
|
44
|
+
--agent-id <id> Stable agent identifier
|
|
45
|
+
--desktop Enable Windows desktop screenshot/input access
|
|
46
|
+
--no-desktop Disable desktop access (default)
|
|
47
|
+
--no-open Do not open the browser automatically
|
|
48
|
+
--force Force a new login
|
|
49
|
+
|
|
50
|
+
Examples:
|
|
51
|
+
npx @anusornneal/chat-relay@latest remote
|
|
52
|
+
npx @anusornneal/chat-relay@latest login
|
|
53
|
+
npx @anusornneal/chat-relay@latest status
|
|
54
|
+
`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function status() {
|
|
58
|
+
const config = loadConfig();
|
|
59
|
+
if (!config) {
|
|
60
|
+
console.log("Chat Relay: not signed in");
|
|
61
|
+
console.log("Access: sign-in required");
|
|
62
|
+
console.log('Recovery: run "chat-relay login", then "chat-relay remote".');
|
|
63
|
+
console.log("Config: " + configPath());
|
|
64
|
+
return 1;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const me = await requestJson(config.relayUrl + "/auth/me", {
|
|
68
|
+
headers: { authorization: "Bearer " + config.userToken },
|
|
69
|
+
}).catch((error) => ({
|
|
70
|
+
response: { ok: false, status: 0 },
|
|
71
|
+
data: { error: error.message },
|
|
72
|
+
}));
|
|
73
|
+
|
|
74
|
+
if (!me.response.ok) {
|
|
75
|
+
console.log("Chat Relay");
|
|
76
|
+
console.log("----------");
|
|
77
|
+
console.log("Relay: " + config.relayUrl);
|
|
78
|
+
console.log("Device: " + config.agentName + " (" + config.agentId + ")");
|
|
79
|
+
if (me.response.status === 401 || me.response.status === 403) {
|
|
80
|
+
console.log("Access: sign-in expired");
|
|
81
|
+
console.log('Recovery: run "chat-relay login --force", then "chat-relay remote".');
|
|
82
|
+
} else {
|
|
83
|
+
console.log("Access: relay unavailable");
|
|
84
|
+
console.log("Recovery: retry status when the relay is reachable.");
|
|
85
|
+
}
|
|
86
|
+
console.log("Config: " + configPath());
|
|
87
|
+
return 1;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const agentStatus = await requestJson(
|
|
91
|
+
config.relayUrl + "/status?agentId=" + encodeURIComponent(config.agentId),
|
|
92
|
+
{ headers: { authorization: "Bearer " + config.userToken } },
|
|
93
|
+
).catch((error) => ({
|
|
94
|
+
response: { ok: false, status: 0 },
|
|
95
|
+
data: { error: error.message, online: false },
|
|
96
|
+
}));
|
|
97
|
+
|
|
98
|
+
const serverAgentName = agentStatus.response.ok && agentStatus.data.agentName
|
|
99
|
+
? String(agentStatus.data.agentName)
|
|
100
|
+
: config.agentName;
|
|
101
|
+
if (agentStatus.response.ok && serverAgentName !== config.agentName) {
|
|
102
|
+
saveConfig({ ...config, agentName: serverAgentName });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const reauthorizationRequired = agentStatus.response.ok
|
|
106
|
+
? agentStatus.data.reauthorizationRequired === true || agentStatus.data.authorized === false
|
|
107
|
+
: agentStatus.response.status === 401 || agentStatus.response.status === 403;
|
|
108
|
+
|
|
109
|
+
console.log("Chat Relay");
|
|
110
|
+
console.log("----------");
|
|
111
|
+
console.log(
|
|
112
|
+
"Account: " +
|
|
113
|
+
(me.data.user?.name || me.data.user?.login || "signed in") +
|
|
114
|
+
" (" + (me.data.user?.id || "unknown") + ")",
|
|
115
|
+
);
|
|
116
|
+
console.log("Login: " + (me.data.user?.login || "-"));
|
|
117
|
+
console.log("Device: " + serverAgentName + " (" + config.agentId + ")");
|
|
118
|
+
console.log("Relay: " + config.relayUrl);
|
|
119
|
+
console.log("Access: " + (reauthorizationRequired ? "re-authorization required" : "authorized"));
|
|
120
|
+
console.log(
|
|
121
|
+
"Remote: " +
|
|
122
|
+
(agentStatus.response.ok && agentStatus.data.online ? "connected" : "offline"),
|
|
123
|
+
);
|
|
124
|
+
if (agentStatus.response.ok) {
|
|
125
|
+
console.log("LastSeen: " + (agentStatus.data.lastSeenAt || "-"));
|
|
126
|
+
console.log(
|
|
127
|
+
"Scopes: " +
|
|
128
|
+
(Array.isArray(agentStatus.data.scopes) ? agentStatus.data.scopes.join(",") : "-"),
|
|
129
|
+
);
|
|
130
|
+
const connection = agentStatus.data.connection || {};
|
|
131
|
+
const compatibility = protocolCompatibility(connection.protocolVersion, agentStatus.data.expectedProtocolVersion);
|
|
132
|
+
console.log("Version: " + (connection.agentVersion || "-"));
|
|
133
|
+
console.log(`Protocol: ${connection.protocolVersion ?? "?"}/${agentStatus.data.expectedProtocolVersion ?? "?"} (${compatibility})`);
|
|
134
|
+
if (agentStatus.data.lifecycle) {
|
|
135
|
+
const lifecycle = agentStatus.data.lifecycle;
|
|
136
|
+
console.log("Lifecycle: " + lifecycle.state + (lifecycle.readyToRestart ? " (ready to restart)" : ""));
|
|
137
|
+
if (lifecycle.work?.total) console.log("ActiveWork: " + lifecycle.work.total);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
console.log("Files: " + config.allowedRoots);
|
|
141
|
+
console.log("Desktop: " + (config.desktopEnabled === true ? "enabled" : "disabled"));
|
|
142
|
+
console.log("Config: " + configPath());
|
|
143
|
+
|
|
144
|
+
if (reauthorizationRequired) {
|
|
145
|
+
console.log('Recovery: run "chat-relay login --force", then "chat-relay remote".');
|
|
146
|
+
return 1;
|
|
147
|
+
}
|
|
148
|
+
if (!agentStatus.response.ok && agentStatus.response.status === 0) {
|
|
149
|
+
console.log("Recovery: retry status when the relay is reachable.");
|
|
150
|
+
return 1;
|
|
151
|
+
}
|
|
152
|
+
return 0;
|
|
153
|
+
}
|
|
154
|
+
async function lifecycleAction(action) {
|
|
155
|
+
const config = loadConfig();
|
|
156
|
+
if (!config) {
|
|
157
|
+
console.error("Chat Relay: not signed in");
|
|
158
|
+
return 1;
|
|
159
|
+
}
|
|
160
|
+
const result = await requestJson(config.relayUrl + "/relay?agentId=" + encodeURIComponent(config.agentId), {
|
|
161
|
+
method: "POST",
|
|
162
|
+
headers: { authorization: "Bearer " + config.userToken, "content-type": "application/json" },
|
|
163
|
+
body: JSON.stringify({ payload: { action: "agent.lifecycle." + action } }),
|
|
164
|
+
}).catch((error) => ({ response: { ok: false, status: 0 }, data: { error: error.message } }));
|
|
165
|
+
if (!result.response.ok) {
|
|
166
|
+
console.error("Lifecycle action failed: " + (result.data?.error || ("HTTP " + result.response.status)));
|
|
167
|
+
return 1;
|
|
168
|
+
}
|
|
169
|
+
const payload = result.data?.payload || result.data;
|
|
170
|
+
if (payload?.ok === false) {
|
|
171
|
+
console.error("Lifecycle action rejected: " + (payload.error || "unknown_error"));
|
|
172
|
+
if (payload.lifecycle?.work?.total) console.error("Active work remaining: " + payload.lifecycle.work.total);
|
|
173
|
+
return 1;
|
|
174
|
+
}
|
|
175
|
+
console.log("Agent lifecycle: " + (payload?.lifecycle?.state || "unknown"));
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function logout() {
|
|
180
|
+
const config = loadConfig();
|
|
181
|
+
if (!config) {
|
|
182
|
+
console.log("Chat Relay: already logged out");
|
|
183
|
+
return 0;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
let revoked = false;
|
|
187
|
+
try {
|
|
188
|
+
const result = await requestJson(`${config.relayUrl}/auth/logout`, {
|
|
189
|
+
method: "POST",
|
|
190
|
+
headers: { authorization: `Bearer ${config.userToken}` },
|
|
191
|
+
body: JSON.stringify({ agentId: config.agentId }),
|
|
192
|
+
});
|
|
193
|
+
revoked = result.response.ok || result.response.status === 401;
|
|
194
|
+
if (!revoked) {
|
|
195
|
+
throw new Error(result.data.error || `HTTP ${result.response.status}`);
|
|
196
|
+
}
|
|
197
|
+
} catch (error) {
|
|
198
|
+
console.error(`Logout could not revoke the server session: ${error instanceof Error ? error.message : error}`);
|
|
199
|
+
console.error("Local credentials were kept. Retry when the relay is reachable.");
|
|
200
|
+
return 1;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
clearConfig();
|
|
204
|
+
console.log("Chat Relay: logged out and local credentials removed");
|
|
205
|
+
return 0;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export async function runCli(argv = process.argv.slice(2)) {
|
|
209
|
+
const command = (argv[0] || "remote").toLowerCase();
|
|
210
|
+
const { options } = parseOptions(argv.slice(1));
|
|
211
|
+
|
|
212
|
+
if (command === "help" || command === "--help" || command === "-h") {
|
|
213
|
+
help();
|
|
214
|
+
return 0;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (command === "login") {
|
|
218
|
+
const current = loadConfig();
|
|
219
|
+
if (!options.force && await validateConfig(current)) {
|
|
220
|
+
console.log(`Already signed in as ${current.user?.name || current.user?.login || "user"}.`);
|
|
221
|
+
console.log("Use --force to sign in again.");
|
|
222
|
+
return 0;
|
|
223
|
+
}
|
|
224
|
+
await login(options);
|
|
225
|
+
return 0;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (command === "logout") return logout();
|
|
229
|
+
if (command === "status") return status();
|
|
230
|
+
if (command === "drain") return lifecycleAction("drain");
|
|
231
|
+
if (command === "resume") return lifecycleAction("resume");
|
|
232
|
+
if (command === "restart") return lifecycleAction("restart");
|
|
233
|
+
if (command === "remote") {
|
|
234
|
+
await remote(options);
|
|
235
|
+
return 0;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
console.error(`Unknown command: ${command}`);
|
|
239
|
+
help();
|
|
240
|
+
return 1;
|
|
241
|
+
}
|