-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall.js
More file actions
203 lines (171 loc) · 7.38 KB
/
Copy pathinstall.js
File metadata and controls
203 lines (171 loc) · 7.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
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
202
203
// install.js
'use strict';
const fs = require('fs');
const path = require('path');
const zlib = require('zlib');
const { execSync } = require('child_process');
const readline = require('readline');
const root = __dirname;
const hostDir = path.join(root, 'host');
const iconsDir = path.join(root, 'extension', 'icons');
const manifestPath = path.join(hostDir, 'com.cdpbridge.host.json');
const batPath = path.join(hostDir, 'run-host.bat');
const extDir = path.join(root, 'extension');
// ── Parse CLI args ────────────────────────────────────────────────────────────
function getExtensionId() {
// 1. --id <value> CLI flag
const idIdx = process.argv.indexOf('--id');
if (idIdx !== -1 && process.argv[idIdx + 1]) return process.argv[idIdx + 1];
// 2. CDP_BRIDGE_EXT_ID environment variable
if (process.env.CDP_BRIDGE_EXT_ID) return process.env.CDP_BRIDGE_EXT_ID;
return null;
}
function promptExtensionId() {
return new Promise((resolve) => {
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
rl.question('Enter your Chrome Extension ID (from chrome://extensions): ', (answer) => {
rl.close();
resolve(answer.trim());
});
});
}
// ── 1. npm install ────────────────────────────────────────────────────────────
console.log('[1/5] Installing host npm dependencies...');
execSync('npm install', { cwd: hostDir, stdio: 'inherit' });
// ── 2. Extension ID ───────────────────────────────────────────────────────────
console.log('[2/5] Configuring extension ID...');
async function main() {
let extId = getExtensionId();
if (!extId) {
extId = await promptExtensionId();
}
if (!extId) {
console.error(' ✗ No extension ID provided. You can set it later by editing:');
console.error(` ${manifestPath}`);
console.error(' or re-running: node install.js --id <your-extension-id>');
process.exit(1);
}
console.log(` → Extension ID: ${extId}`);
// ── 3. Generate PNG icons ─────────────────────────────────────────────────────
console.log('[3/5] Generating icons...');
function crc32(buf) {
const table = new Uint32Array(256);
for (let i = 0; i < 256; i++) {
let c = i;
for (let j = 0; j < 8; j++) c = c & 1 ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1);
table[i] = c;
}
let crc = 0xffffffff;
for (const b of buf) crc = table[(crc ^ b) & 0xff] ^ (crc >>> 8);
return (crc ^ 0xffffffff) >>> 0;
}
function pngChunk(type, data) {
const t = Buffer.from(type, 'ascii');
const len = Buffer.alloc(4);
const crc = Buffer.alloc(4);
len.writeUInt32BE(data.length);
crc.writeUInt32BE(crc32(Buffer.concat([t, data])));
return Buffer.concat([len, t, data, crc]);
}
function makePNG(size, r, g, b) {
const sig = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
const ihdr = Buffer.alloc(13);
ihdr.writeUInt32BE(size, 0);
ihdr.writeUInt32BE(size, 4);
ihdr[8] = 8; ihdr[9] = 2; // 8-bit RGB
const raw = Buffer.alloc(size * (1 + 3 * size));
for (let y = 0; y < size; y++) {
const base = y * (1 + 3 * size);
raw[base] = 0; // filter: None
for (let x = 0; x < size; x++) {
raw[base + 1 + x * 3] = r;
raw[base + 1 + x * 3 + 1] = g;
raw[base + 1 + x * 3 + 2] = b;
}
}
return Buffer.concat([
sig,
pngChunk('IHDR', ihdr),
pngChunk('IDAT', zlib.deflateSync(raw)),
pngChunk('IEND', Buffer.alloc(0))
]);
}
fs.mkdirSync(iconsDir, { recursive: true });
for (const size of [16, 48, 128]) {
fs.writeFileSync(
path.join(iconsDir, `icon${size}.png`),
makePNG(size, 99, 102, 241) // indigo #6366f1
);
}
// ── 4. Update manifest path + allowed_origins ─────────────────────────────────
console.log('[4/5] Updating native host manifest...');
const isWindows = process.platform === 'win32';
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
if (isWindows) {
manifest.path = batPath.replace(/\\/g, '/');
} else {
// On Linux/macOS, create a shell launcher script
const shPath = path.join(hostDir, 'run-host.sh');
fs.writeFileSync(shPath, `#!/bin/sh\nexec node "$(dirname "$0")/host.js" 2>>"$(dirname "$0")/host.log"\n`);
fs.chmodSync(shPath, 0o755);
manifest.path = shPath;
}
manifest.allowed_origins = [`chrome-extension://${extId}/`];
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
// ── 5. Register native messaging host ────────────────────────────────────────
console.log('[5/5] Registering native messaging host...');
if (isWindows) {
const regKey = 'HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\com.cdpbridge.host';
execSync(`reg add "${regKey}" /ve /t REG_SZ /d "${manifestPath}" /f`, { stdio: 'inherit' });
} else {
// On Linux/macOS, symlink the manifest into the Chrome NativeMessagingHosts dir
const home = require('os').homedir();
const nmDirs = [];
if (process.platform === 'darwin') {
nmDirs.push(path.join(home, 'Library', 'Application Support', 'Google', 'Chrome', 'NativeMessagingHosts'));
nmDirs.push(path.join(home, 'Library', 'Application Support', 'Chromium', 'NativeMessagingHosts'));
} else {
nmDirs.push(path.join(home, '.config', 'google-chrome', 'NativeMessagingHosts'));
nmDirs.push(path.join(home, '.config', 'chromium', 'NativeMessagingHosts'));
}
let installed = false;
for (const dir of nmDirs) {
// Only install into dirs whose parent browser config exists
if (!fs.existsSync(path.dirname(dir))) continue;
fs.mkdirSync(dir, { recursive: true });
const dest = path.join(dir, 'com.cdpbridge.host.json');
try { fs.unlinkSync(dest); } catch (_) {}
fs.symlinkSync(manifestPath, dest);
console.log(` → Symlinked manifest into ${dir}`);
installed = true;
}
if (!installed) {
console.warn(' ⚠ No Chrome/Chromium config directory found. You may need to manually copy the manifest.');
}
}
console.log(`
================================================
CDP Bridge — Installation Complete
================================================
NEXT STEPS:
1. Open chrome://extensions in Chrome
2. Enable "Developer mode" (top-right toggle)
3. Click "Load unpacked" and select:
${extDir}
4. Click the reload icon (↺) on the extension card
The bridge starts automatically when the extension loads.
HTTP API available at: http://localhost:1232
ENDPOINTS:
GET /health Liveness check
GET /tabs List open tabs
POST /command Execute a CDP command
Body: { "tabId": N, "method": "...", "params": {} }
USAGE (from Claude Code or any HTTP client):
curl http://localhost:1232/tabs
curl -X POST http://localhost:1232/command \\
-H "Content-Type: application/json" \\
-d '{"tabId":N,"method":"Runtime.evaluate","params":{"expression":"document.title"}}'
================================================
`);
} // end main
main();