@chatroomcp/chatroom 0.2.0 → 0.2.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.
@@ -15,9 +15,10 @@ export declare class ComputerNativeHost {
15
15
  restart(reason?: string): void;
16
16
  dispose(): Promise<void>;
17
17
  private ensureStarted;
18
+ private startHelper;
18
19
  private startMacHelper;
19
20
  private attachSocket;
20
- private startWindowsHelper;
21
+ private startPipeHelper;
21
22
  private failTransport;
22
23
  private reset;
23
24
  private rejectPending;
@@ -1,5 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { spawn } from "node:child_process";
2
+ import { spawn, spawnSync, } from "node:child_process";
3
3
  import { chmodSync, existsSync, rmSync } from "node:fs";
4
4
  import { createServer } from "node:net";
5
5
  import { createInterface } from "node:readline";
@@ -21,6 +21,8 @@ export class ComputerNativeHost {
21
21
  return "macos";
22
22
  if (process.platform === "win32")
23
23
  return "windows";
24
+ if (process.platform === "linux")
25
+ return "linux";
24
26
  return "unsupported";
25
27
  }
26
28
  get idle() {
@@ -59,18 +61,16 @@ export class ComputerNativeHost {
59
61
  this.reset(new Error("Computer helper stopped"));
60
62
  }
61
63
  async ensureStarted() {
62
- if (this.platform === "macos" && this.socket && !this.socket.destroyed)
64
+ const platform = this.platform;
65
+ if (platform === "macos" && this.socket && !this.socket.destroyed)
63
66
  return;
64
- if (this.platform === "windows" && this.child && !this.child.killed)
67
+ if ((platform === "windows" || platform === "linux") &&
68
+ this.child &&
69
+ !this.child.killed)
65
70
  return;
66
71
  if (this.starting)
67
72
  return this.starting;
68
- this.starting =
69
- this.platform === "macos"
70
- ? this.startMacHelper()
71
- : this.platform === "windows"
72
- ? this.startWindowsHelper()
73
- : Promise.reject(new ChatRoomError("UNSUPPORTED", "Computer Use is supported only on macOS and Windows"));
73
+ this.starting = this.startHelper(platform);
74
74
  try {
75
75
  await this.starting;
76
76
  }
@@ -78,6 +78,18 @@ export class ComputerNativeHost {
78
78
  this.starting = null;
79
79
  }
80
80
  }
81
+ startHelper(platform) {
82
+ switch (platform) {
83
+ case "macos":
84
+ return this.startMacHelper();
85
+ case "windows":
86
+ return this.startPipeHelper(windowsHelperPath(), "Windows");
87
+ case "linux":
88
+ return this.startPipeHelper(linuxHelperPath(), "Linux X11", linuxDesktopEnvironment());
89
+ case "unsupported":
90
+ return Promise.reject(new ChatRoomError("UNSUPPORTED", "Computer Use is supported only on macOS, Windows, and Linux X11"));
91
+ }
92
+ }
81
93
  async startMacHelper() {
82
94
  const app = macHelperAppPath();
83
95
  if (!app)
@@ -131,13 +143,13 @@ export class ComputerNativeHost {
131
143
  socket.once("error", (error) => this.failTransport(socket, error));
132
144
  socket.once("close", () => this.failTransport(socket, new Error("Computer helper exited")));
133
145
  }
134
- async startWindowsHelper() {
135
- const executable = windowsHelperPath();
146
+ async startPipeHelper(executable, platformName, env = process.env) {
136
147
  if (!executable)
137
- throw new ChatRoomError("UNSUPPORTED", "Windows Computer helper is missing");
148
+ throw new ChatRoomError("UNSUPPORTED", `${platformName} Computer helper is missing`);
138
149
  const child = spawn(executable, [], {
139
150
  stdio: ["pipe", "pipe", "pipe"],
140
- windowsHide: true,
151
+ windowsHide: process.platform === "win32",
152
+ env,
141
153
  });
142
154
  this.child = child;
143
155
  this.lines = createInterface({ input: child.stdout });
@@ -236,10 +248,88 @@ function projectRoot() {
236
248
  return path.resolve(path.dirname(current), "../../..");
237
249
  }
238
250
  function macHelperAppPath() {
239
- const app = path.join(projectRoot(), "dist", "native", "macos", "ChatRoomComputerHelper.app");
240
- return existsSync(app) ? app : null;
251
+ return nativeHelperPath("macos", "ChatRoomComputerHelper.app");
241
252
  }
242
253
  function windowsHelperPath() {
243
- const executable = path.join(projectRoot(), "dist", "native", "windows", "chatroom-computer-helper.exe");
244
- return existsSync(executable) ? executable : null;
254
+ return nativeHelperPath("windows", "chatroom-computer-helper.exe");
255
+ }
256
+ function linuxHelperPath() {
257
+ return nativeHelperPath("linux", "chatroom-computer-helper");
258
+ }
259
+ function nativeHelperPath(...parts) {
260
+ const target = path.join(projectRoot(), "dist", "native", ...parts);
261
+ return existsSync(target) ? target : null;
262
+ }
263
+ function linuxDesktopEnvironment() {
264
+ const env = { ...process.env };
265
+ const uid = typeof process.getuid === "function" ? process.getuid() : null;
266
+ if (!env.XDG_RUNTIME_DIR && uid !== null) {
267
+ const runtimeDir = `/run/user/${uid}`;
268
+ if (existsSync(runtimeDir))
269
+ env.XDG_RUNTIME_DIR = runtimeDir;
270
+ }
271
+ if (!env.DISPLAY) {
272
+ const display = activeX11Display(uid);
273
+ if (display) {
274
+ env.DISPLAY = display;
275
+ env.XDG_SESSION_TYPE = "x11";
276
+ }
277
+ }
278
+ if (!env.XAUTHORITY) {
279
+ const candidates = [
280
+ env.HOME ? path.join(env.HOME, ".Xauthority") : null,
281
+ env.XDG_RUNTIME_DIR
282
+ ? path.join(env.XDG_RUNTIME_DIR, "gdm", "Xauthority")
283
+ : null,
284
+ ];
285
+ const authority = candidates.find((candidate) => !!candidate && existsSync(candidate));
286
+ if (authority)
287
+ env.XAUTHORITY = authority;
288
+ }
289
+ if (!env.DBUS_SESSION_BUS_ADDRESS && env.XDG_RUNTIME_DIR) {
290
+ const bus = path.join(env.XDG_RUNTIME_DIR, "bus");
291
+ if (existsSync(bus))
292
+ env.DBUS_SESSION_BUS_ADDRESS = `unix:path=${bus}`;
293
+ }
294
+ return env;
295
+ }
296
+ function activeX11Display(uid) {
297
+ if (process.platform !== "linux" || uid === null)
298
+ return null;
299
+ const loginctl = existsSync("/usr/bin/loginctl")
300
+ ? "/usr/bin/loginctl"
301
+ : "loginctl";
302
+ const sessions = spawnSync(loginctl, ["list-sessions", "--no-legend", "--no-pager"], {
303
+ encoding: "utf8",
304
+ timeout: 2_000,
305
+ });
306
+ if (sessions.status !== 0 || !sessions.stdout)
307
+ return null;
308
+ const userId = String(uid);
309
+ for (const line of sessions.stdout.split("\n")) {
310
+ const [sessionId, sessionUid] = line.trim().split(/\s+/, 3);
311
+ if (!sessionId || sessionUid !== userId)
312
+ continue;
313
+ const details = spawnSync(loginctl, [
314
+ "show-session",
315
+ sessionId,
316
+ "--no-pager",
317
+ "-p",
318
+ "Type",
319
+ "-p",
320
+ "Active",
321
+ "-p",
322
+ "Display",
323
+ ], { encoding: "utf8", timeout: 2_000 });
324
+ if (details.status !== 0 || !details.stdout)
325
+ continue;
326
+ const values = Object.fromEntries(details.stdout
327
+ .split("\n")
328
+ .map((item) => item.split("=", 2))
329
+ .filter((item) => item.length === 2));
330
+ if (values.Type === "x11" && values.Active === "yes" && values.Display) {
331
+ return values.Display;
332
+ }
333
+ }
334
+ return null;
245
335
  }
@@ -36,6 +36,7 @@ export declare const computerNativeStatusSchema: z.ZodObject<{
36
36
  platform: z.ZodEnum<{
37
37
  macos: "macos";
38
38
  windows: "windows";
39
+ linux: "linux";
39
40
  unsupported: "unsupported";
40
41
  }>;
41
42
  helper: z.ZodEnum<{
@@ -30,7 +30,7 @@ export const computerScreenshotSchema = z.object({
30
30
  data: z.string(),
31
31
  });
32
32
  export const computerNativeStatusSchema = z.object({
33
- platform: z.enum(["macos", "windows", "unsupported"]),
33
+ platform: z.enum(["macos", "windows", "linux", "unsupported"]),
34
34
  helper: z.enum(["running", "stopped", "unavailable"]),
35
35
  permissions: z.object({
36
36
  accessibility: computerPermissionStateSchema,
@@ -1,5 +1,5 @@
1
1
  export type ComputerAccessScope = "local" | "remote";
2
- export type ComputerPlatform = "macos" | "windows" | "unsupported";
2
+ export type ComputerPlatform = "macos" | "windows" | "linux" | "unsupported";
3
3
  export type ComputerHelperState = "running" | "stopped" | "unavailable";
4
4
  export type ComputerPermissionState = "granted" | "denied" | "unknown" | "not-required";
5
5
  export type ComputerPermission = "accessibility" | "screenRecording";