Tool to send cross-session opencode messages, including as request-response pattern
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

add common Sensor trait and update implementations

rektide 665b3136 cb8301f9

+118 -118
+3
src/sensor/index.ts
··· 1 + export { Instance, Sensor } from "./trait.js"; 2 + export { MdnsSensor } from "./mdns.js"; 3 + export { ProcSensor } from "./proc.js";
+33 -38
src/sensor/mdns.ts
··· 1 - import { Browser, Service } from "bonjour-service"; 1 + import { Browser } from "bonjour-service"; 2 + import { Instance, Sensor } from "./trait.js"; 2 3 3 - export interface Instance { 4 - port: number; 5 - hostname?: string; 6 - type: "mdns"; 7 - } 4 + export class MdnsSensor implements Sensor { 5 + private browser?: Browser; 8 6 9 - export class MdnsSensor { 10 - private browser?: Browser; 7 + async discover(timeout = 5000): Promise<Instance[]> { 8 + const instances: Instance[] = []; 9 + const bonjour = new Browser(); 11 10 12 - async discover(timeout = 5000): Promise<Instance[]> { 13 - const instances: Instance[] = []; 14 - const bonjour = new Browser(); 11 + this.browser = bonjour; 15 12 16 - this.browser = bonjour; 13 + return new Promise((resolve) => { 14 + setTimeout(() => { 15 + this.stop(); 16 + resolve(instances); 17 + }, timeout); 17 18 18 - return new Promise((resolve) => { 19 - const timer = setTimeout(() => { 20 - this.stop(); 21 - resolve(instances); 22 - }, timeout); 19 + bonjour.find({ type: "http", protocol: "tcp" }, (service) => { 20 + if (!service.name.includes("opencode")) { 21 + return; 22 + } 23 23 24 - bonjour.find({ type: "http", protocol: "tcp" }, (service) => { 25 - if (!service.name.includes("opencode")) { 26 - return; 27 - } 24 + if (service.port) { 25 + instances.push({ 26 + port: service.port, 27 + hostname: service.host, 28 + source: "mdns", 29 + }); 30 + } 31 + }); 32 + }); 33 + } 28 34 29 - if (service.port) { 30 - instances.push({ 31 - port: service.port, 32 - hostname: service.host, 33 - type: "mdns", 34 - }); 35 - } 36 - }); 37 - }); 38 - } 39 - 40 - stop(): void { 41 - if (this.browser) { 42 - this.browser.stop(); 43 - this.browser = undefined; 44 - } 45 - } 35 + stop(): void { 36 + if (this.browser) { 37 + this.browser.stop(); 38 + this.browser = undefined; 39 + } 40 + } 46 41 }
+70 -80
src/sensor/proc.ts
··· 1 1 import { readdir, readFile } from "node:fs/promises"; 2 2 import { join } from "node:path"; 3 - 4 - export interface Instance { 5 - pid: number; 6 - port?: number; 7 - cwd?: string; 8 - type: "proc"; 9 - } 3 + import { Instance, Sensor } from "./trait.js"; 10 4 11 5 interface ProcInfo { 12 - pid: number; 13 - cmdline: string[]; 14 - cwd?: string; 6 + pid: number; 7 + cmdline: string[]; 8 + cwd?: string; 15 9 } 16 10 17 11 async function readProcCmdline(pid: number): Promise<string[]> { 18 - try { 19 - const cmdlinePath = join("/proc", pid.toString(), "cmdline"); 20 - const content = await readFile(cmdlinePath, "utf-8"); 21 - return content.split("\0").filter((arg) => arg.length > 0); 22 - } catch { 23 - return []; 24 - } 12 + try { 13 + const cmdlinePath = join("/proc", pid.toString(), "cmdline"); 14 + const content = await readFile(cmdlinePath, "utf-8"); 15 + return content.split("\0").filter((arg) => arg.length > 0); 16 + } catch { 17 + return []; 18 + } 25 19 } 26 20 27 21 async function readProcCwd(pid: number): Promise<string | undefined> { 28 - try { 29 - const cwdPath = join("/proc", pid.toString(), "cwd"); 30 - const linkTarget = await readFile(cwdPath, "utf-8"); 31 - return linkTarget.trim(); 32 - } catch { 33 - return undefined; 34 - } 22 + try { 23 + const cwdPath = join("/proc", pid.toString(), "cwd"); 24 + const linkTarget = await readFile(cwdPath, "utf-8"); 25 + return linkTarget.trim(); 26 + } catch { 27 + return undefined; 28 + } 35 29 } 36 30 37 31 async function getOpenCodeProcesses(): Promise<ProcInfo[]> { 38 - const processes: ProcInfo[] = []; 32 + const processes: ProcInfo[] = []; 39 33 40 - try { 41 - const procDir = "/proc"; 42 - const entries = await readdir(procDir, { withFileTypes: true }); 34 + try { 35 + const procDir = "/proc"; 36 + const entries = await readdir(procDir, { withFileTypes: true }); 43 37 44 - for (const entry of entries) { 45 - if (!entry.isDirectory() || !/^\d+$/.test(entry.name)) { 46 - continue; 47 - } 38 + for (const entry of entries) { 39 + if (!entry.isDirectory() || !/^\d+$/.test(entry.name)) { 40 + continue; 41 + } 48 42 49 - const pid = parseInt(entry.name, 10); 50 - const cmdline = await readProcCmdline(pid); 43 + const pid = parseInt(entry.name, 10); 44 + const cmdline = await readProcCmdline(pid); 51 45 52 - if (cmdline.length === 0) { 53 - continue; 54 - } 46 + if (cmdline.length === 0) { 47 + continue; 48 + } 55 49 56 - const cmd = cmdline[0].toLowerCase(); 57 - if ( 58 - !cmd.includes("opencode") && 59 - !cmd.includes("bun") && 60 - !cmd.includes("node") 61 - ) { 62 - continue; 63 - } 50 + const cmd = cmdline[0].toLowerCase(); 51 + if (!cmd.includes("opencode") && !cmd.includes("bun") && !cmd.includes("node")) { 52 + continue; 53 + } 64 54 65 - const args = cmdline.slice(1).join(" ").toLowerCase(); 66 - if (!args.includes("opencode")) { 67 - continue; 68 - } 55 + const args = cmdline.slice(1).join(" ").toLowerCase(); 56 + if (!args.includes("opencode")) { 57 + continue; 58 + } 69 59 70 - const cwd = await readProcCwd(pid); 71 - processes.push({ pid, cmdline, cwd }); 72 - } 73 - } catch (error) { 74 - return []; 75 - } 60 + const cwd = await readProcCwd(pid); 61 + processes.push({ pid, cmdline, cwd }); 62 + } 63 + } catch { 64 + return []; 65 + } 76 66 77 - return processes; 67 + return processes; 78 68 } 79 69 80 70 function extractPortFromArgs(args: string[]): number | undefined { 81 - for (let i = 0; i < args.length; i++) { 82 - if (args[i] === "--port" && i + 1 < args.length) { 83 - const port = parseInt(args[i + 1], 10); 84 - if (!isNaN(port)) { 85 - return port; 86 - } 87 - } 88 - } 89 - return undefined; 71 + for (let i = 0; i < args.length; i++) { 72 + if (args[i] === "--port" && i + 1 < args.length) { 73 + const port = parseInt(args[i + 1], 10); 74 + if (!isNaN(port)) { 75 + return port; 76 + } 77 + } 78 + } 79 + return undefined; 90 80 } 91 81 92 - export class ProcSensor { 93 - async discover(): Promise<Instance[]> { 94 - const processes = await getOpenCodeProcesses(); 95 - const instances: Instance[] = []; 82 + export class ProcSensor implements Sensor { 83 + async discover(): Promise<Instance[]> { 84 + const processes = await getOpenCodeProcesses(); 85 + const instances: Instance[] = []; 96 86 97 - for (const proc of processes) { 98 - const port = extractPortFromArgs(proc.cmdline); 99 - instances.push({ 100 - pid: proc.pid, 101 - port, 102 - cwd: proc.cwd, 103 - type: "proc", 104 - }); 105 - } 87 + for (const proc of processes) { 88 + const port = extractPortFromArgs(proc.cmdline); 89 + instances.push({ 90 + port: port ?? 4096, 91 + pid: proc.pid, 92 + cwd: proc.cwd, 93 + source: "proc", 94 + }); 95 + } 106 96 107 - return instances; 108 - } 97 + return instances; 98 + } 109 99 }
+12
src/sensor/trait.ts
··· 1 + export interface Instance { 2 + port: number; 3 + hostname?: string; 4 + pid?: number; 5 + cwd?: string; 6 + source: "mdns" | "proc" | "port" | "lockfile"; 7 + } 8 + 9 + export interface Sensor { 10 + discover(timeout?: number): Promise<Instance[]>; 11 + stop?(): void; 12 + }