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 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 claude desktop
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 the json configs
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "syncme-mcp",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "copy your logins for a site into a separate chrome that your ai agents and scripts can use",
5
5
  "type": "module",
6
6
  "bin": {
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 claude = process.platform === 'win32' ? join(process.env.APPDATA || homedir(), 'Claude', 'claude_desktop_config.json') : join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
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] of [['claude desktop', claude], ['cursor', join(homedir(), '.cursor', 'mcp.json')], ['windsurf', join(homedir(), '.codeium', 'windsurf', 'mcp_config.json')]]) {
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.mcpServers && typeof existing.mcpServers === 'object' && !Array.isArray(existing.mcpServers) ? existing.mcpServers : {};
415
- writeFileSync(file, JSON.stringify({...existing, mcpServers: {...servers, syncme: SERVER}}, null, 2) + '\n');
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] of [['claude', ['mcp', 'add', '--scope', 'user', 'syncme', '--', 'npx', '-y', 'syncme-mcp']], ['codex', ['mcp', 'add', 'syncme', '--', 'npx', '-y', 'syncme-mcp']]]) {
422
- const result = spawnSync(name, args, {stdio: 'ignore'});
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
  }