MCPcopy Create free account
hub / github.com/garrytan/gstack / BrowseClient

Class BrowseClient

browse/src/browse-client.ts:132–228  ·  view source on GitHub ↗

Source from the content-addressed store, hash-verified

130 * etc.). For anything not exposed as a method, use `command(cmd, args)`.
131 */
132export class BrowseClient {
133 readonly port: number;
134 readonly token: string;
135 readonly tabId?: number;
136 readonly timeoutMs: number;
137
138 constructor(opts: BrowseClientOptions = {}) {
139 const auth = resolveBrowseAuth(opts);
140 this.port = auth.port;
141 this.token = auth.token;
142 this.tabId = opts.tabId ?? parseIntegerEnvValue(process.env.BROWSE_TAB);
143 this.timeoutMs = opts.timeoutMs ?? 30_000;
144 }
145
146 // ─── Low-level dispatch ─────────────────────────────────────────
147
148 /** Send an arbitrary command; returns raw response text. Throws on non-2xx. */
149 async command(cmd: string, args: string[] = []): Promise<string> {
150 const body = JSON.stringify({
151 command: cmd,
152 args,
153 ...(this.tabId !== undefined && !isNaN(this.tabId) ? { tabId: this.tabId } : {}),
154 });
155
156 let resp: Response;
157 try {
158 resp = await fetch(`http://127.0.0.1:${this.port}/command`, {
159 method: 'POST',
160 headers: {
161 'Content-Type': 'application/json',
162 'Authorization': `Bearer ${this.token}`,
163 },
164 body,
165 signal: AbortSignal.timeout(this.timeoutMs),
166 });
167 } catch (err: any) {
168 if (err.name === 'TimeoutError' || err.name === 'AbortError') {
169 throw new BrowseClientError(`browse-client: command "${cmd}" timed out after ${this.timeoutMs}ms`);
170 }
171 if (err.code === 'ECONNREFUSED') {
172 throw new BrowseClientError(`browse-client: daemon not running on port ${this.port}`);
173 }
174 throw new BrowseClientError(`browse-client: ${err.message ?? err}`);
175 }
176
177 const text = await resp.text();
178 if (!resp.ok) {
179 let message = `browse-client: command "${cmd}" failed with status ${resp.status}`;
180 try {
181 const parsed = JSON.parse(text);
182 if (parsed.error) message += `: ${parsed.error}`;
183 } catch {
184 if (text) message += `: ${text.slice(0, 200)}`;
185 }
186 throw new BrowseClientError(message, resp.status, text);
187 }
188 return text;
189 }

Callers

nothing calls this directly

Calls

no outgoing calls

Tested by

no test coverage detected