syncme-mcp 1.0.2 → 1.0.3
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 +14 -14
- package/e2e/setup.sh +43 -0
- package/options.js +3 -3
- package/package.json +1 -1
- package/syncme.js +26 -7
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ syncme comes in two parts: the [chrome extension](https://chromewebstore.google.
|
|
|
8
8
|
|
|
9
9
|
1. when you're on a site you want, click the syncme icon and it asks if you want to copy it over
|
|
10
10
|
2. syncme grabs that site's cookies and puts them in its own chrome profile (the "ai browser"). this runs headless in the background, nothing pops up
|
|
11
|
-
3. when a script needs the browser, run `npx -y syncme-mcp open`. the ai browser opens as normal chrome with your logins already in it and prints a cdp url to connect to
|
|
11
|
+
3. when a script needs the browser, run `npx -y syncme-mcp@latest open`. the ai browser opens as normal chrome with your logins already in it and prints a cdp url to connect to
|
|
12
12
|
|
|
13
13
|
it only copies cookies for sites you said yes to. no passwords, no other sites. syncme never touches your real chrome profile.
|
|
14
14
|
|
|
@@ -21,18 +21,18 @@ needs chrome and node 20 or newer. works on mac and windows.
|
|
|
21
21
|
there's nothing to install: the extension talks to the bridge over a websocket on 127.0.0.1, and any command starts the bridge itself.
|
|
22
22
|
|
|
23
23
|
1. install the extension from the [chrome web store](https://chromewebstore.google.com/detail/dcgmgkdgmbjgnchjdmokimppnhfinpfp), or load this folder unpacked from `chrome://extensions` with developer mode on
|
|
24
|
-
2. click the syncme icon, hit the gear, and use one of the connect buttons, or run `npx -y syncme-mcp setup` to wire up claude code, codex, cursor, windsurf and
|
|
24
|
+
2. click the syncme icon, hit the gear, and use one of the connect buttons, or run `npx -y syncme-mcp@latest setup` to wire up every ai app it finds: claude code, claude desktop, codex, hermes, cursor, windsurf, vs code, cline, roo code, copilot cli, gemini cli, qwen code, kiro, opencode and amp
|
|
25
25
|
|
|
26
26
|
## commands
|
|
27
27
|
|
|
28
28
|
```
|
|
29
|
-
npx -y syncme-mcp open # start the ai browser, prints {"cdp": "http://127.0.0.1:<port>"}
|
|
30
|
-
npx -y syncme-mcp sync https://example.com # copy one site, asks you in chrome first unless it's saved or yolo is on
|
|
31
|
-
npx -y syncme-mcp stop # close the ai browser
|
|
32
|
-
npx -y syncme-mcp shutdown # close it and stop the bridge
|
|
33
|
-
npx -y syncme-mcp setup # add the mcp server to the ai apps you have installed
|
|
34
|
-
npx -y syncme-mcp unpair # forget the paired extension, for when you reinstall it
|
|
35
|
-
npx -y syncme-mcp pack # build syncme.zip for the web store
|
|
29
|
+
npx -y syncme-mcp@latest open # start the ai browser, prints {"cdp": "http://127.0.0.1:<port>"}
|
|
30
|
+
npx -y syncme-mcp@latest sync https://example.com # copy one site, asks you in chrome first unless it's saved or yolo is on
|
|
31
|
+
npx -y syncme-mcp@latest stop # close the ai browser
|
|
32
|
+
npx -y syncme-mcp@latest shutdown # close it and stop the bridge
|
|
33
|
+
npx -y syncme-mcp@latest setup # add the mcp server to the ai apps you have installed
|
|
34
|
+
npx -y syncme-mcp@latest unpair # forget the paired extension, for when you reinstall it
|
|
35
|
+
npx -y syncme-mcp@latest pack # build syncme.zip for the web store
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
connecting from playwright:
|
|
@@ -41,7 +41,7 @@ connecting from playwright:
|
|
|
41
41
|
import {chromium} from 'playwright-core';
|
|
42
42
|
import {execFileSync} from 'node:child_process';
|
|
43
43
|
|
|
44
|
-
const {cdp} = JSON.parse(execFileSync('npx', ['-y', 'syncme-mcp', 'open']));
|
|
44
|
+
const {cdp} = JSON.parse(execFileSync('npx', ['-y', 'syncme-mcp@latest', 'open']));
|
|
45
45
|
const browser = await chromium.connectOverCDP(cdp);
|
|
46
46
|
await (await browser.contexts()[0].newPage()).goto('https://example.com');
|
|
47
47
|
```
|
|
@@ -51,9 +51,9 @@ await (await browser.contexts()[0].newPage()).goto('https://example.com');
|
|
|
51
51
|
you can also hook it up as an mcp server so your ai can ask for a site itself. it calls `copy_site` with something like `https://github.com`, you get a popup in chrome asking if it can have that login, and if you say yes it copies it and gets back the cdp url. turn on yolo mode in the syncme options (the gear in the popup) to skip that popup and let your ai copy any site it asks for. there's also `open_browser` and `close_browser`.
|
|
52
52
|
|
|
53
53
|
```
|
|
54
|
-
claude mcp add --scope user syncme -- npx -y syncme-mcp
|
|
55
|
-
codex mcp add syncme -- npx -y syncme-mcp
|
|
56
|
-
npx -y syncme-mcp setup # does both of the above, plus
|
|
54
|
+
claude mcp add --scope user syncme -- npx -y syncme-mcp@latest
|
|
55
|
+
codex mcp add syncme -- npx -y syncme-mcp@latest
|
|
56
|
+
npx -y syncme-mcp@latest setup # does both of the above, plus every other app it finds
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
or by hand:
|
|
@@ -63,7 +63,7 @@ or by hand:
|
|
|
63
63
|
"mcpServers": {
|
|
64
64
|
"syncme": {
|
|
65
65
|
"command": "npx",
|
|
66
|
-
"args": ["-y", "syncme-mcp"]
|
|
66
|
+
"args": ["-y", "syncme-mcp@latest"]
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
}
|
package/e2e/setup.sh
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# runs `syncme-mcp setup` against a throwaway home with fake agent clis and checks every config it writes
|
|
3
|
+
set -eu
|
|
4
|
+
root=$(cd "$(dirname "$0")/.." && pwd)
|
|
5
|
+
work=$(mktemp -d)
|
|
6
|
+
trap 'rm -rf "$work"' EXIT
|
|
7
|
+
home="$work/home"
|
|
8
|
+
bin="$work/bin"
|
|
9
|
+
support="$home/Library/Application Support"
|
|
10
|
+
mkdir -p "$bin" "$support/Claude" "$support/Code/User/globalStorage/saoudrizwan.claude-dev/settings" \
|
|
11
|
+
"$support/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings" "$home/.cursor" "$home/.codeium/windsurf" \
|
|
12
|
+
"$home/.gemini" "$home/.qwen" "$home/.kiro/settings" "$home/.copilot" "$home/.config/opencode" "$home/.config/amp"
|
|
13
|
+
echo '{"theme": "dark", "mcpServers": {"other": {"command": "x"}}}' > "$home/.gemini/settings.json"
|
|
14
|
+
for cli in claude codex hermes code; do
|
|
15
|
+
printf '#!/bin/sh\necho "$0 $*" >> "%s/calls"\n/bin/cat >> "%s/stdin"\n' "$work" "$work" > "$bin/$cli"
|
|
16
|
+
chmod +x "$bin/$cli"
|
|
17
|
+
done
|
|
18
|
+
ln -s "$(command -v node)" "$bin/node"
|
|
19
|
+
HOME="$home" PATH="$bin" node "$root/syncme.js" setup > "$work/out.json"
|
|
20
|
+
cat "$work/out.json"
|
|
21
|
+
HOME="$home" WORK="$work" node --input-type=module -e '
|
|
22
|
+
import {readFileSync} from "node:fs";
|
|
23
|
+
import assert from "node:assert/strict";
|
|
24
|
+
const home = process.env.HOME, work = process.env.WORK, support = home + "/Library/Application Support";
|
|
25
|
+
const read = file => JSON.parse(readFileSync(file, "utf8"));
|
|
26
|
+
const out = read(work + "/out.json");
|
|
27
|
+
const server = {command: "npx", args: ["-y", "syncme-mcp@latest"]};
|
|
28
|
+
assert.deepEqual(out.configured, ["claude desktop", "cursor", "windsurf", "gemini", "qwen", "kiro", "copilot cli", "opencode", "amp", "cline", "roo code", "claude cli", "codex cli", "hermes cli", "code cli"]);
|
|
29
|
+
assert.deepEqual(out.failed, []);
|
|
30
|
+
for (const file of [support + "/Claude/claude_desktop_config.json", home + "/.cursor/mcp.json", home + "/.codeium/windsurf/mcp_config.json", home + "/.qwen/settings.json", home + "/.kiro/settings/mcp.json", support + "/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json", support + "/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/mcp_settings.json"])
|
|
31
|
+
assert.deepEqual(read(file).mcpServers.syncme, server, file);
|
|
32
|
+
assert.deepEqual(read(home + "/.gemini/settings.json"), {theme: "dark", mcpServers: {other: {command: "x"}, syncme: server}});
|
|
33
|
+
assert.deepEqual(read(home + "/.copilot/mcp-config.json").mcpServers.syncme, {type: "local", ...server, tools: ["*"]});
|
|
34
|
+
assert.deepEqual(read(home + "/.config/opencode/opencode.json").mcp.syncme, {type: "local", command: ["npx", "-y", "syncme-mcp@latest"], enabled: true});
|
|
35
|
+
assert.deepEqual(read(home + "/.config/amp/settings.json")["amp.mcpServers"].syncme, server);
|
|
36
|
+
const calls = readFileSync(work + "/calls", "utf8");
|
|
37
|
+
assert.match(calls, /claude mcp add --scope user syncme -- npx -y syncme-mcp@latest/);
|
|
38
|
+
assert.match(calls, /codex mcp add syncme -- npx -y syncme-mcp@latest/);
|
|
39
|
+
assert.match(calls, /hermes mcp add syncme --command npx --args -y syncme-mcp@latest/);
|
|
40
|
+
assert.match(calls, /code --add-mcp \{"name":"syncme","command":"npx","args":\["-y","syncme-mcp@latest"\]\}/);
|
|
41
|
+
assert.equal(readFileSync(work + "/stdin", "utf8"), "y\n");
|
|
42
|
+
console.log("setup e2e passed");
|
|
43
|
+
'
|
package/options.js
CHANGED
|
@@ -22,13 +22,13 @@ for (const [id, key] of [['auto', 'autoSync'], ['yolo', 'yolo']]) {
|
|
|
22
22
|
chrome.storage.local.get(key).then(stored => { box.checked = stored[key] === true; });
|
|
23
23
|
box.onchange = () => chrome.storage.local.set({[key]: box.checked});
|
|
24
24
|
}
|
|
25
|
-
document.querySelector('#claude').onclick = () => navigator.clipboard.writeText('claude mcp add syncme -- npx -y syncme-mcp');
|
|
26
|
-
document.querySelector('#setup').onclick = () => navigator.clipboard.writeText('npx -y syncme-mcp setup');
|
|
25
|
+
document.querySelector('#claude').onclick = () => navigator.clipboard.writeText('claude mcp add syncme -- npx -y syncme-mcp@latest');
|
|
26
|
+
document.querySelector('#setup').onclick = () => navigator.clipboard.writeText('npx -y syncme-mcp@latest setup');
|
|
27
27
|
chrome.storage.session.get(['connected', 'pairing']).then(({connected, pairing}) => {
|
|
28
28
|
document.querySelector('#waiting').hidden = connected === true || Boolean(pairing);
|
|
29
29
|
const box = document.querySelector('#pairing');
|
|
30
30
|
box.hidden = !pairing;
|
|
31
|
-
box.firstChild.textContent = pairing === 'lost' ? 'syncme lost its pairing with this extension.' : 'syncme is paired with a different extension. Run npx -y syncme-mcp unpair, then hit Re-pair.';
|
|
31
|
+
box.firstChild.textContent = pairing === 'lost' ? 'syncme lost its pairing with this extension.' : 'syncme is paired with a different extension. Run npx -y syncme-mcp@latest unpair, then hit Re-pair.';
|
|
32
32
|
});
|
|
33
33
|
document.querySelector('#repair').onclick = () => { chrome.runtime.sendMessage({op: 'repair'}); document.querySelector('#pairing').hidden = true; };
|
|
34
34
|
chrome.storage.onChanged.addListener(changes => { if (changes.approved) render(); });
|
package/package.json
CHANGED
package/syncme.js
CHANGED
|
@@ -19,7 +19,7 @@ const PORT = 47621;
|
|
|
19
19
|
const LIMIT = 1024 * 1024;
|
|
20
20
|
const CLIENT_URL = 'ws://127.0.0.1:' + PORT + '/client';
|
|
21
21
|
const BRIDGE_URL = 'ws://127.0.0.1:' + PORT + '/';
|
|
22
|
-
const SERVER = {command: 'npx', args: ['-y', 'syncme-mcp']};
|
|
22
|
+
const SERVER = {command: 'npx', args: ['-y', 'syncme-mcp@latest']};
|
|
23
23
|
const SAME_SITE = {no_restriction: 'None', lax: 'Lax', strict: 'Strict', unspecified: 'Lax'};
|
|
24
24
|
|
|
25
25
|
const rejected = phase => Object.assign(Error('Rejected'), {phase});
|
|
@@ -403,23 +403,42 @@ function pack() {
|
|
|
403
403
|
}
|
|
404
404
|
|
|
405
405
|
function setup() {
|
|
406
|
-
const
|
|
406
|
+
const home = homedir();
|
|
407
|
+
const appData = process.platform === 'win32' ? process.env.APPDATA || join(home, 'AppData', 'Roaming') : join(home, 'Library', 'Application Support');
|
|
408
|
+
const vscode = join(appData, 'Code', 'User');
|
|
407
409
|
const configured = [];
|
|
408
410
|
const failed = [];
|
|
409
|
-
for (const [name, file
|
|
411
|
+
for (const [name, file, key, server] of [
|
|
412
|
+
['claude desktop', join(appData, 'Claude', 'claude_desktop_config.json'), 'mcpServers', SERVER],
|
|
413
|
+
['cursor', join(home, '.cursor', 'mcp.json'), 'mcpServers', SERVER],
|
|
414
|
+
['windsurf', join(home, '.codeium', 'windsurf', 'mcp_config.json'), 'mcpServers', SERVER],
|
|
415
|
+
['gemini', join(home, '.gemini', 'settings.json'), 'mcpServers', SERVER],
|
|
416
|
+
['qwen', join(home, '.qwen', 'settings.json'), 'mcpServers', SERVER],
|
|
417
|
+
['kiro', join(home, '.kiro', 'settings', 'mcp.json'), 'mcpServers', SERVER],
|
|
418
|
+
['copilot cli', join(home, '.copilot', 'mcp-config.json'), 'mcpServers', {type: 'local', ...SERVER, tools: ['*']}],
|
|
419
|
+
['opencode', join(home, '.config', 'opencode', 'opencode.json'), 'mcp', {type: 'local', command: [SERVER.command, ...SERVER.args], enabled: true}],
|
|
420
|
+
['amp', join(home, '.config', 'amp', 'settings.json'), 'amp.mcpServers', SERVER],
|
|
421
|
+
['cline', join(vscode, 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'), 'mcpServers', SERVER],
|
|
422
|
+
['roo code', join(vscode, 'globalStorage', 'rooveterinaryinc.roo-cline', 'settings', 'mcp_settings.json'), 'mcpServers', SERVER],
|
|
423
|
+
]) {
|
|
410
424
|
if (!existsSync(dirname(file))) continue;
|
|
411
425
|
try {
|
|
412
426
|
const existing = existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) : {};
|
|
413
427
|
if (!existing || typeof existing !== 'object' || Array.isArray(existing)) throw Error('Unreadable config');
|
|
414
|
-
const servers = existing
|
|
415
|
-
writeFileSync(file, JSON.stringify({...existing,
|
|
428
|
+
const servers = existing[key] && typeof existing[key] === 'object' && !Array.isArray(existing[key]) ? existing[key] : {};
|
|
429
|
+
writeFileSync(file, JSON.stringify({...existing, [key]: {...servers, syncme: server}}, null, 2) + '\n');
|
|
416
430
|
configured.push(name);
|
|
417
431
|
} catch {
|
|
418
432
|
failed.push(file);
|
|
419
433
|
}
|
|
420
434
|
}
|
|
421
|
-
for (const [name, args
|
|
422
|
-
|
|
435
|
+
for (const [name, args, input] of [
|
|
436
|
+
['claude', ['mcp', 'add', '--scope', 'user', 'syncme', '--', 'npx', '-y', 'syncme-mcp@latest']],
|
|
437
|
+
['codex', ['mcp', 'add', 'syncme', '--', 'npx', '-y', 'syncme-mcp@latest']],
|
|
438
|
+
['hermes', ['mcp', 'add', 'syncme', '--command', 'npx', '--args', '-y', 'syncme-mcp@latest'], 'y\n'],
|
|
439
|
+
['code', ['--add-mcp', JSON.stringify({name: 'syncme', ...SERVER})]],
|
|
440
|
+
]) {
|
|
441
|
+
const result = spawnSync(name, args, {input: input || '', stdio: ['pipe', 'ignore', 'ignore'], timeout: 120000});
|
|
423
442
|
if (result.status === 0) configured.push(name + ' cli');
|
|
424
443
|
else if (!result.error || result.error.code !== 'ENOENT') failed.push(name + ' cli');
|
|
425
444
|
}
|