hanzi-browse 2.2.0
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 +182 -0
- package/dist/agent/loop.d.ts +63 -0
- package/dist/agent/loop.js +186 -0
- package/dist/agent/system-prompt.d.ts +7 -0
- package/dist/agent/system-prompt.js +41 -0
- package/dist/agent/tools.d.ts +9 -0
- package/dist/agent/tools.js +154 -0
- package/dist/cli/detect-credentials.d.ts +31 -0
- package/dist/cli/detect-credentials.js +44 -0
- package/dist/cli/import-credentials-handler.d.ts +14 -0
- package/dist/cli/import-credentials-handler.js +22 -0
- package/dist/cli/session-files.d.ts +28 -0
- package/dist/cli/session-files.js +118 -0
- package/dist/cli/setup.d.ts +10 -0
- package/dist/cli/setup.js +915 -0
- package/dist/cli.d.ts +16 -0
- package/dist/cli.js +506 -0
- package/dist/dashboard/assets/index-CEFyesbT.js +46 -0
- package/dist/dashboard/assets/index-Dnht2kLU.css +1 -0
- package/dist/dashboard/index.html +13 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1116 -0
- package/dist/ipc/index.d.ts +8 -0
- package/dist/ipc/index.js +8 -0
- package/dist/ipc/native-host.d.ts +96 -0
- package/dist/ipc/native-host.js +223 -0
- package/dist/ipc/websocket-client.d.ts +73 -0
- package/dist/ipc/websocket-client.js +199 -0
- package/dist/license/manager.d.ts +20 -0
- package/dist/license/manager.js +15 -0
- package/dist/llm/client.d.ts +72 -0
- package/dist/llm/client.js +227 -0
- package/dist/llm/credentials.d.ts +61 -0
- package/dist/llm/credentials.js +200 -0
- package/dist/llm/vertex.d.ts +22 -0
- package/dist/llm/vertex.js +335 -0
- package/dist/managed/api-http.test.d.ts +7 -0
- package/dist/managed/api-http.test.js +623 -0
- package/dist/managed/api.d.ts +51 -0
- package/dist/managed/api.js +1448 -0
- package/dist/managed/api.test.d.ts +10 -0
- package/dist/managed/api.test.js +146 -0
- package/dist/managed/auth.d.ts +38 -0
- package/dist/managed/auth.js +192 -0
- package/dist/managed/billing.d.ts +70 -0
- package/dist/managed/billing.js +227 -0
- package/dist/managed/deploy.d.ts +17 -0
- package/dist/managed/deploy.js +385 -0
- package/dist/managed/e2e.test.d.ts +15 -0
- package/dist/managed/e2e.test.js +151 -0
- package/dist/managed/hardening.test.d.ts +14 -0
- package/dist/managed/hardening.test.js +346 -0
- package/dist/managed/integration.test.d.ts +8 -0
- package/dist/managed/integration.test.js +274 -0
- package/dist/managed/log.d.ts +18 -0
- package/dist/managed/log.js +31 -0
- package/dist/managed/server.d.ts +12 -0
- package/dist/managed/server.js +69 -0
- package/dist/managed/store-pg.d.ts +191 -0
- package/dist/managed/store-pg.js +479 -0
- package/dist/managed/store.d.ts +188 -0
- package/dist/managed/store.js +379 -0
- package/dist/relay/auto-start.d.ts +19 -0
- package/dist/relay/auto-start.js +71 -0
- package/dist/relay/server.d.ts +17 -0
- package/dist/relay/server.js +403 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.js +4 -0
- package/dist/types/session.d.ts +134 -0
- package/dist/types/session.js +16 -0
- package/package.json +61 -0
- package/skills/README.md +48 -0
- package/skills/a11y-auditor/SKILL.md +42 -0
- package/skills/e2e-tester/SKILL.md +154 -0
- package/skills/hanzi-browse/SKILL.md +182 -0
- package/skills/linkedin-prospector/SKILL.md +149 -0
- package/skills/social-poster/SKILL.md +146 -0
- package/skills/x-marketer/SKILL.md +479 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IPC Module - Communication with Chrome Extension
|
|
3
|
+
*
|
|
4
|
+
* WebSocketClient is the primary transport for MCP traffic.
|
|
5
|
+
* NativeHostConnection is retained only for legacy utility flows.
|
|
6
|
+
*/
|
|
7
|
+
export { WebSocketClient, type WebSocketClientOptions } from './websocket-client.js';
|
|
8
|
+
export { NativeHostConnection, type NativeMessage, type MessageHandler, type ConnectionOptions, type OutgoingMessageType, type IncomingMessageType, } from './native-host.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IPC Module - Communication with Chrome Extension
|
|
3
|
+
*
|
|
4
|
+
* WebSocketClient is the primary transport for MCP traffic.
|
|
5
|
+
* NativeHostConnection is retained only for legacy utility flows.
|
|
6
|
+
*/
|
|
7
|
+
export { WebSocketClient } from './websocket-client.js';
|
|
8
|
+
export { NativeHostConnection, } from './native-host.js';
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native Host IPC Module
|
|
3
|
+
*
|
|
4
|
+
* Handles communication with the installed native host via Chrome native
|
|
5
|
+
* messaging protocol.
|
|
6
|
+
*
|
|
7
|
+
* Protocol: Chrome Native Messaging (4-byte little-endian length prefix + JSON)
|
|
8
|
+
*
|
|
9
|
+
* This transport is no longer used for MCP task delivery. It remains for
|
|
10
|
+
* native-host-backed utilities such as credential reads, debug logging, and
|
|
11
|
+
* API proxy support.
|
|
12
|
+
*/
|
|
13
|
+
/** Message types sent TO the native host */
|
|
14
|
+
export type OutgoingMessageType = 'ping' | 'debug_log' | 'agent_log' | 'check_file' | 'read_cli_credentials' | 'read_codex_credentials' | 'proxy_api_call';
|
|
15
|
+
/** Message types received FROM the native host */
|
|
16
|
+
export type IncomingMessageType = 'pong' | 'debug_logged' | 'file_check_result' | 'cli_credentials' | 'codex_credentials' | 'credentials_not_found' | 'api_response' | 'api_error' | 'error';
|
|
17
|
+
/** Base message structure */
|
|
18
|
+
export interface NativeMessage {
|
|
19
|
+
type: string;
|
|
20
|
+
sessionId?: string;
|
|
21
|
+
requestId?: string;
|
|
22
|
+
[key: string]: any;
|
|
23
|
+
}
|
|
24
|
+
/** Callback for handling incoming messages */
|
|
25
|
+
export type MessageHandler = (message: NativeMessage) => void | Promise<void>;
|
|
26
|
+
/** Connection options */
|
|
27
|
+
export interface ConnectionOptions {
|
|
28
|
+
/** Custom path to native host executable */
|
|
29
|
+
hostPath?: string;
|
|
30
|
+
/** Callback when connection is lost */
|
|
31
|
+
onDisconnect?: (code: number | null) => void;
|
|
32
|
+
/** Callback for native host stderr output */
|
|
33
|
+
onStderr?: (data: string) => void;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Manages a connection to the native host process.
|
|
37
|
+
*
|
|
38
|
+
* Usage:
|
|
39
|
+
* const conn = new NativeHostConnection();
|
|
40
|
+
* conn.onMessage((msg) => console.log('Received:', msg));
|
|
41
|
+
* await conn.connect();
|
|
42
|
+
* await conn.send({ type: 'ping' });
|
|
43
|
+
*/
|
|
44
|
+
export declare class NativeHostConnection {
|
|
45
|
+
private process;
|
|
46
|
+
private messageBuffer;
|
|
47
|
+
private messageHandlers;
|
|
48
|
+
private options;
|
|
49
|
+
private connected;
|
|
50
|
+
constructor(options?: ConnectionOptions);
|
|
51
|
+
/**
|
|
52
|
+
* Find the native host executable path from the installed Chrome manifest
|
|
53
|
+
*/
|
|
54
|
+
private findHostPath;
|
|
55
|
+
/**
|
|
56
|
+
* Register a handler for incoming messages
|
|
57
|
+
*/
|
|
58
|
+
onMessage(handler: MessageHandler): void;
|
|
59
|
+
/**
|
|
60
|
+
* Remove a message handler
|
|
61
|
+
*/
|
|
62
|
+
offMessage(handler: MessageHandler): void;
|
|
63
|
+
/**
|
|
64
|
+
* Connect to the native host process
|
|
65
|
+
*/
|
|
66
|
+
connect(): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Process buffered messages using the native messaging protocol
|
|
69
|
+
* (4-byte little-endian length prefix + JSON payload)
|
|
70
|
+
*/
|
|
71
|
+
private processMessages;
|
|
72
|
+
/**
|
|
73
|
+
* Dispatch a message to all registered handlers
|
|
74
|
+
*/
|
|
75
|
+
private dispatchMessage;
|
|
76
|
+
/**
|
|
77
|
+
* Send a message to the native host
|
|
78
|
+
*/
|
|
79
|
+
send(message: NativeMessage): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Check if connected to native host
|
|
82
|
+
*/
|
|
83
|
+
isConnected(): boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Disconnect from the native host
|
|
86
|
+
*/
|
|
87
|
+
disconnect(): void;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get the default native host connection (creates one if needed)
|
|
91
|
+
*/
|
|
92
|
+
export declare function getDefaultConnection(): NativeHostConnection;
|
|
93
|
+
/**
|
|
94
|
+
* Reset the default connection (useful for testing)
|
|
95
|
+
*/
|
|
96
|
+
export declare function resetDefaultConnection(): void;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native Host IPC Module
|
|
3
|
+
*
|
|
4
|
+
* Handles communication with the installed native host via Chrome native
|
|
5
|
+
* messaging protocol.
|
|
6
|
+
*
|
|
7
|
+
* Protocol: Chrome Native Messaging (4-byte little-endian length prefix + JSON)
|
|
8
|
+
*
|
|
9
|
+
* This transport is no longer used for MCP task delivery. It remains for
|
|
10
|
+
* native-host-backed utilities such as credential reads, debug logging, and
|
|
11
|
+
* API proxy support.
|
|
12
|
+
*/
|
|
13
|
+
import { spawn } from 'child_process';
|
|
14
|
+
import { existsSync, readFileSync } from 'fs';
|
|
15
|
+
import { join } from 'path';
|
|
16
|
+
import { homedir } from 'os';
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Native Host Connection Class
|
|
19
|
+
// ============================================================================
|
|
20
|
+
/**
|
|
21
|
+
* Manages a connection to the native host process.
|
|
22
|
+
*
|
|
23
|
+
* Usage:
|
|
24
|
+
* const conn = new NativeHostConnection();
|
|
25
|
+
* conn.onMessage((msg) => console.log('Received:', msg));
|
|
26
|
+
* await conn.connect();
|
|
27
|
+
* await conn.send({ type: 'ping' });
|
|
28
|
+
*/
|
|
29
|
+
export class NativeHostConnection {
|
|
30
|
+
process = null;
|
|
31
|
+
messageBuffer = Buffer.alloc(0);
|
|
32
|
+
messageHandlers = [];
|
|
33
|
+
options;
|
|
34
|
+
connected = false;
|
|
35
|
+
constructor(options = {}) {
|
|
36
|
+
this.options = options;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Find the native host executable path from the installed Chrome manifest
|
|
40
|
+
*/
|
|
41
|
+
findHostPath() {
|
|
42
|
+
// Check user-provided path first
|
|
43
|
+
if (this.options.hostPath && existsSync(this.options.hostPath)) {
|
|
44
|
+
return this.options.hostPath;
|
|
45
|
+
}
|
|
46
|
+
// Look for Chrome native messaging manifest
|
|
47
|
+
const manifestPath = join(homedir(), 'Library', 'Application Support', 'Google', 'Chrome', 'NativeMessagingHosts', 'com.hanzi_browse.oauth_host.json');
|
|
48
|
+
if (existsSync(manifestPath)) {
|
|
49
|
+
try {
|
|
50
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
51
|
+
if (manifest.path && existsSync(manifest.path)) {
|
|
52
|
+
return manifest.path;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Fall through to error
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
throw new Error('Native host not found. Please install the Chrome extension first.\n' +
|
|
60
|
+
'Expected manifest at: ' + manifestPath);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Register a handler for incoming messages
|
|
64
|
+
*/
|
|
65
|
+
onMessage(handler) {
|
|
66
|
+
this.messageHandlers.push(handler);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Remove a message handler
|
|
70
|
+
*/
|
|
71
|
+
offMessage(handler) {
|
|
72
|
+
const index = this.messageHandlers.indexOf(handler);
|
|
73
|
+
if (index !== -1) {
|
|
74
|
+
this.messageHandlers.splice(index, 1);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Connect to the native host process
|
|
79
|
+
*/
|
|
80
|
+
async connect() {
|
|
81
|
+
if (this.connected && this.process?.stdin?.writable) {
|
|
82
|
+
return; // Already connected
|
|
83
|
+
}
|
|
84
|
+
const hostPath = this.findHostPath();
|
|
85
|
+
console.error(`[NativeHost] Connecting to: ${hostPath}`);
|
|
86
|
+
return new Promise((resolve, reject) => {
|
|
87
|
+
try {
|
|
88
|
+
this.process = spawn(hostPath, [], {
|
|
89
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
90
|
+
});
|
|
91
|
+
this.process.stdout?.on('data', (chunk) => {
|
|
92
|
+
this.messageBuffer = Buffer.concat([this.messageBuffer, chunk]);
|
|
93
|
+
this.processMessages();
|
|
94
|
+
});
|
|
95
|
+
this.process.stderr?.on('data', (data) => {
|
|
96
|
+
const text = data.toString().trim();
|
|
97
|
+
if (this.options.onStderr) {
|
|
98
|
+
this.options.onStderr(text);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
console.error(`[NativeHost] ${text}`);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
this.process.on('error', (err) => {
|
|
105
|
+
console.error('[NativeHost] Process error:', err.message);
|
|
106
|
+
this.connected = false;
|
|
107
|
+
reject(err);
|
|
108
|
+
});
|
|
109
|
+
this.process.on('close', (code) => {
|
|
110
|
+
console.error(`[NativeHost] Process exited with code: ${code}`);
|
|
111
|
+
this.connected = false;
|
|
112
|
+
this.process = null;
|
|
113
|
+
if (this.options.onDisconnect) {
|
|
114
|
+
this.options.onDisconnect(code);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
this.connected = true;
|
|
118
|
+
// Give the process a moment to initialize
|
|
119
|
+
setTimeout(() => resolve(), 100);
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
reject(err);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Process buffered messages using the native messaging protocol
|
|
128
|
+
* (4-byte little-endian length prefix + JSON payload)
|
|
129
|
+
*/
|
|
130
|
+
processMessages() {
|
|
131
|
+
while (this.messageBuffer.length >= 4) {
|
|
132
|
+
const msgLen = this.messageBuffer.readUInt32LE(0);
|
|
133
|
+
if (this.messageBuffer.length < 4 + msgLen) {
|
|
134
|
+
break; // Wait for more data
|
|
135
|
+
}
|
|
136
|
+
const msgStr = this.messageBuffer.subarray(4, 4 + msgLen).toString();
|
|
137
|
+
this.messageBuffer = this.messageBuffer.subarray(4 + msgLen);
|
|
138
|
+
try {
|
|
139
|
+
const message = JSON.parse(msgStr);
|
|
140
|
+
this.dispatchMessage(message);
|
|
141
|
+
}
|
|
142
|
+
catch (e) {
|
|
143
|
+
console.error('[NativeHost] Failed to parse message:', e);
|
|
144
|
+
console.error('[NativeHost] Raw message (first 200 chars):', msgStr.substring(0, 200));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Dispatch a message to all registered handlers
|
|
150
|
+
*/
|
|
151
|
+
async dispatchMessage(message) {
|
|
152
|
+
for (const handler of this.messageHandlers) {
|
|
153
|
+
try {
|
|
154
|
+
await handler(message);
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
console.error('[NativeHost] Handler error:', err);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Send a message to the native host
|
|
163
|
+
*/
|
|
164
|
+
async send(message) {
|
|
165
|
+
if (!this.process?.stdin?.writable) {
|
|
166
|
+
await this.connect();
|
|
167
|
+
}
|
|
168
|
+
const json = JSON.stringify(message);
|
|
169
|
+
const buffer = Buffer.from(json);
|
|
170
|
+
const len = Buffer.alloc(4);
|
|
171
|
+
len.writeUInt32LE(buffer.length, 0);
|
|
172
|
+
try {
|
|
173
|
+
this.process.stdin.write(len);
|
|
174
|
+
this.process.stdin.write(buffer);
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
console.error('[NativeHost] Send failed:', err.message);
|
|
178
|
+
this.connected = false;
|
|
179
|
+
this.process = null;
|
|
180
|
+
throw new Error(`Failed to send message: ${err.message}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Check if connected to native host
|
|
185
|
+
*/
|
|
186
|
+
isConnected() {
|
|
187
|
+
return this.connected && !!this.process?.stdin?.writable;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Disconnect from the native host
|
|
191
|
+
*/
|
|
192
|
+
disconnect() {
|
|
193
|
+
if (this.process) {
|
|
194
|
+
this.process.kill();
|
|
195
|
+
this.process = null;
|
|
196
|
+
}
|
|
197
|
+
this.connected = false;
|
|
198
|
+
this.messageBuffer = Buffer.alloc(0);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// ============================================================================
|
|
202
|
+
// Convenience Functions
|
|
203
|
+
// ============================================================================
|
|
204
|
+
/** Singleton instance for simple usage */
|
|
205
|
+
let defaultConnection = null;
|
|
206
|
+
/**
|
|
207
|
+
* Get the default native host connection (creates one if needed)
|
|
208
|
+
*/
|
|
209
|
+
export function getDefaultConnection() {
|
|
210
|
+
if (!defaultConnection) {
|
|
211
|
+
defaultConnection = new NativeHostConnection();
|
|
212
|
+
}
|
|
213
|
+
return defaultConnection;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Reset the default connection (useful for testing)
|
|
217
|
+
*/
|
|
218
|
+
export function resetDefaultConnection() {
|
|
219
|
+
if (defaultConnection) {
|
|
220
|
+
defaultConnection.disconnect();
|
|
221
|
+
defaultConnection = null;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Client for Relay Communication
|
|
3
|
+
*
|
|
4
|
+
* Drop-in replacement for NativeHostConnection that communicates
|
|
5
|
+
* via the WebSocket relay server instead of native messaging.
|
|
6
|
+
*
|
|
7
|
+
* Same interface: connect(), send(), onMessage(), isConnected(), disconnect()
|
|
8
|
+
*/
|
|
9
|
+
import type { NativeMessage, MessageHandler, ConnectionOptions } from './native-host.js';
|
|
10
|
+
type ClientRole = 'mcp' | 'cli';
|
|
11
|
+
export interface WebSocketClientOptions extends ConnectionOptions {
|
|
12
|
+
/** Role to register as with the relay */
|
|
13
|
+
role: ClientRole;
|
|
14
|
+
/** Custom relay URL (defaults to ws://localhost:7862) */
|
|
15
|
+
relayUrl?: string;
|
|
16
|
+
/** Auto-start relay server if not running (default: true) */
|
|
17
|
+
autoStartRelay?: boolean;
|
|
18
|
+
/** Extra fields to include in the register message (e.g., relay_secret) */
|
|
19
|
+
registerExtra?: Record<string, string>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* WebSocket-based connection to the Chrome extension via relay server.
|
|
23
|
+
*
|
|
24
|
+
* Usage:
|
|
25
|
+
* const client = new WebSocketClient({ role: 'mcp' });
|
|
26
|
+
* client.onMessage((msg) => console.log('Received:', msg));
|
|
27
|
+
* await client.connect();
|
|
28
|
+
* await client.send({ type: 'mcp_start_task', sessionId: 'abc', task: '...' });
|
|
29
|
+
*/
|
|
30
|
+
export declare class WebSocketClient {
|
|
31
|
+
private ws;
|
|
32
|
+
private messageHandlers;
|
|
33
|
+
private options;
|
|
34
|
+
private connected;
|
|
35
|
+
private reconnectTimer;
|
|
36
|
+
private reconnectAttempts;
|
|
37
|
+
private maxReconnectDelay;
|
|
38
|
+
constructor(options: WebSocketClientOptions);
|
|
39
|
+
/**
|
|
40
|
+
* Register a handler for incoming messages
|
|
41
|
+
*/
|
|
42
|
+
onMessage(handler: MessageHandler): void;
|
|
43
|
+
/**
|
|
44
|
+
* Remove a message handler
|
|
45
|
+
*/
|
|
46
|
+
offMessage(handler: MessageHandler): void;
|
|
47
|
+
/**
|
|
48
|
+
* Connect to the relay server.
|
|
49
|
+
* Auto-starts the relay if needed.
|
|
50
|
+
*/
|
|
51
|
+
connect(): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Dispatch a message to all registered handlers
|
|
54
|
+
*/
|
|
55
|
+
private dispatchMessage;
|
|
56
|
+
/**
|
|
57
|
+
* Send a message to the extension via relay
|
|
58
|
+
*/
|
|
59
|
+
send(message: NativeMessage): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Check if connected to relay
|
|
62
|
+
*/
|
|
63
|
+
isConnected(): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Disconnect from relay
|
|
66
|
+
*/
|
|
67
|
+
disconnect(): void;
|
|
68
|
+
/**
|
|
69
|
+
* Schedule a reconnection attempt with exponential backoff
|
|
70
|
+
*/
|
|
71
|
+
private scheduleReconnect;
|
|
72
|
+
}
|
|
73
|
+
export {};
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Client for Relay Communication
|
|
3
|
+
*
|
|
4
|
+
* Drop-in replacement for NativeHostConnection that communicates
|
|
5
|
+
* via the WebSocket relay server instead of native messaging.
|
|
6
|
+
*
|
|
7
|
+
* Same interface: connect(), send(), onMessage(), isConnected(), disconnect()
|
|
8
|
+
*/
|
|
9
|
+
import WebSocket from 'ws';
|
|
10
|
+
import { ensureRelayRunning, getRelayUrl } from '../relay/auto-start.js';
|
|
11
|
+
/**
|
|
12
|
+
* WebSocket-based connection to the Chrome extension via relay server.
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* const client = new WebSocketClient({ role: 'mcp' });
|
|
16
|
+
* client.onMessage((msg) => console.log('Received:', msg));
|
|
17
|
+
* await client.connect();
|
|
18
|
+
* await client.send({ type: 'mcp_start_task', sessionId: 'abc', task: '...' });
|
|
19
|
+
*/
|
|
20
|
+
export class WebSocketClient {
|
|
21
|
+
ws = null;
|
|
22
|
+
messageHandlers = [];
|
|
23
|
+
options;
|
|
24
|
+
connected = false;
|
|
25
|
+
reconnectTimer = null;
|
|
26
|
+
reconnectAttempts = 0;
|
|
27
|
+
maxReconnectDelay = 30000; // Max 30 second backoff
|
|
28
|
+
constructor(options) {
|
|
29
|
+
this.options = {
|
|
30
|
+
autoStartRelay: true,
|
|
31
|
+
...options,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Register a handler for incoming messages
|
|
36
|
+
*/
|
|
37
|
+
onMessage(handler) {
|
|
38
|
+
this.messageHandlers.push(handler);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Remove a message handler
|
|
42
|
+
*/
|
|
43
|
+
offMessage(handler) {
|
|
44
|
+
const index = this.messageHandlers.indexOf(handler);
|
|
45
|
+
if (index !== -1) {
|
|
46
|
+
this.messageHandlers.splice(index, 1);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Connect to the relay server.
|
|
51
|
+
* Auto-starts the relay if needed.
|
|
52
|
+
*/
|
|
53
|
+
async connect() {
|
|
54
|
+
if (this.connected && this.ws?.readyState === WebSocket.OPEN) {
|
|
55
|
+
return; // Already connected
|
|
56
|
+
}
|
|
57
|
+
const relayUrl = this.options.relayUrl || getRelayUrl();
|
|
58
|
+
// Auto-start relay if configured
|
|
59
|
+
if (this.options.autoStartRelay) {
|
|
60
|
+
try {
|
|
61
|
+
await ensureRelayRunning(relayUrl);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
console.error(`[WSClient] Failed to start relay: ${err.message}`);
|
|
65
|
+
throw err;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
try {
|
|
70
|
+
console.error(`[WSClient] Connecting to relay: ${relayUrl}`);
|
|
71
|
+
this.ws = new WebSocket(relayUrl);
|
|
72
|
+
const connectTimeout = setTimeout(() => {
|
|
73
|
+
if (!this.connected) {
|
|
74
|
+
this.ws?.terminate();
|
|
75
|
+
reject(new Error('WebSocket connection timed out'));
|
|
76
|
+
}
|
|
77
|
+
}, 5000);
|
|
78
|
+
this.ws.on('open', () => {
|
|
79
|
+
clearTimeout(connectTimeout);
|
|
80
|
+
this.connected = true;
|
|
81
|
+
this.reconnectAttempts = 0;
|
|
82
|
+
// Register with relay
|
|
83
|
+
this.ws.send(JSON.stringify({
|
|
84
|
+
type: 'register',
|
|
85
|
+
role: this.options.role,
|
|
86
|
+
...this.options.registerExtra,
|
|
87
|
+
}));
|
|
88
|
+
console.error(`[WSClient] Connected as ${this.options.role}`);
|
|
89
|
+
resolve();
|
|
90
|
+
});
|
|
91
|
+
this.ws.on('message', (data) => {
|
|
92
|
+
try {
|
|
93
|
+
const message = JSON.parse(data.toString());
|
|
94
|
+
// Skip relay protocol messages
|
|
95
|
+
if (message.type === 'registered')
|
|
96
|
+
return;
|
|
97
|
+
if (message.type === 'error') {
|
|
98
|
+
console.error(`[WSClient] Relay error: ${message.error}`);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
this.dispatchMessage(message);
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
console.error('[WSClient] Failed to parse message:', e);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
this.ws.on('close', () => {
|
|
108
|
+
console.error('[WSClient] Disconnected from relay');
|
|
109
|
+
this.connected = false;
|
|
110
|
+
this.ws = null;
|
|
111
|
+
if (this.options.onDisconnect) {
|
|
112
|
+
this.options.onDisconnect(null);
|
|
113
|
+
}
|
|
114
|
+
// Schedule reconnect with exponential backoff
|
|
115
|
+
this.scheduleReconnect();
|
|
116
|
+
});
|
|
117
|
+
this.ws.on('error', (err) => {
|
|
118
|
+
clearTimeout(connectTimeout);
|
|
119
|
+
console.error(`[WSClient] WebSocket error: ${err.message}`);
|
|
120
|
+
if (!this.connected) {
|
|
121
|
+
reject(err);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
reject(err);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Dispatch a message to all registered handlers
|
|
132
|
+
*/
|
|
133
|
+
async dispatchMessage(message) {
|
|
134
|
+
for (const handler of this.messageHandlers) {
|
|
135
|
+
try {
|
|
136
|
+
await handler(message);
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
console.error('[WSClient] Handler error:', err);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Send a message to the extension via relay
|
|
145
|
+
*/
|
|
146
|
+
async send(message) {
|
|
147
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
148
|
+
// Try to reconnect
|
|
149
|
+
try {
|
|
150
|
+
await this.connect();
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
throw new Error('Not connected to relay and reconnection failed');
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
this.ws.send(JSON.stringify(message));
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Check if connected to relay
|
|
160
|
+
*/
|
|
161
|
+
isConnected() {
|
|
162
|
+
return this.connected && this.ws?.readyState === WebSocket.OPEN;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Disconnect from relay
|
|
166
|
+
*/
|
|
167
|
+
disconnect() {
|
|
168
|
+
if (this.reconnectTimer) {
|
|
169
|
+
clearTimeout(this.reconnectTimer);
|
|
170
|
+
this.reconnectTimer = null;
|
|
171
|
+
}
|
|
172
|
+
if (this.ws) {
|
|
173
|
+
this.ws.close();
|
|
174
|
+
this.ws = null;
|
|
175
|
+
}
|
|
176
|
+
this.connected = false;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Schedule a reconnection attempt with exponential backoff
|
|
180
|
+
*/
|
|
181
|
+
scheduleReconnect() {
|
|
182
|
+
if (this.reconnectTimer)
|
|
183
|
+
return;
|
|
184
|
+
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), this.maxReconnectDelay);
|
|
185
|
+
this.reconnectAttempts++;
|
|
186
|
+
console.error(`[WSClient] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
|
|
187
|
+
this.reconnectTimer = setTimeout(async () => {
|
|
188
|
+
this.reconnectTimer = null;
|
|
189
|
+
try {
|
|
190
|
+
await this.connect();
|
|
191
|
+
console.error('[WSClient] Reconnected successfully');
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
console.error(`[WSClient] Reconnection failed: ${err.message}`);
|
|
195
|
+
// onclose handler will schedule next attempt
|
|
196
|
+
}
|
|
197
|
+
}, delay);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* License Manager for MCP Server (BYOM mode)
|
|
3
|
+
*
|
|
4
|
+
* BYOM is free and unlimited — no license check needed.
|
|
5
|
+
* Managed mode uses the per-task credit system in api.ts instead.
|
|
6
|
+
*
|
|
7
|
+
* This file is kept for backwards compatibility (index.ts imports it)
|
|
8
|
+
* but always returns "allowed".
|
|
9
|
+
*/
|
|
10
|
+
export declare function checkAndIncrementUsage(): Promise<{
|
|
11
|
+
allowed: boolean;
|
|
12
|
+
remaining: number | null;
|
|
13
|
+
message: string;
|
|
14
|
+
}>;
|
|
15
|
+
export declare function getLicenseStatus(): {
|
|
16
|
+
isPro: boolean;
|
|
17
|
+
tasksUsed: number;
|
|
18
|
+
taskLimit: number | null;
|
|
19
|
+
message: string;
|
|
20
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* License Manager for MCP Server (BYOM mode)
|
|
3
|
+
*
|
|
4
|
+
* BYOM is free and unlimited — no license check needed.
|
|
5
|
+
* Managed mode uses the per-task credit system in api.ts instead.
|
|
6
|
+
*
|
|
7
|
+
* This file is kept for backwards compatibility (index.ts imports it)
|
|
8
|
+
* but always returns "allowed".
|
|
9
|
+
*/
|
|
10
|
+
export async function checkAndIncrementUsage() {
|
|
11
|
+
return { allowed: true, remaining: null, message: "BYOM — unlimited tasks" };
|
|
12
|
+
}
|
|
13
|
+
export function getLicenseStatus() {
|
|
14
|
+
return { isPro: true, tasksUsed: 0, taskLimit: null, message: "BYOM — Free, unlimited" };
|
|
15
|
+
}
|