hivemind-cli 0.1.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.
Files changed (3) hide show
  1. package/README.md +63 -0
  2. package/dist/index.js +66 -0
  3. package/package.json +37 -0
package/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # hivemind-cli
2
+
3
+ **One memory across every AI tool you use.**
4
+
5
+ Tell Claude Code something. Cursor already knows it. So does ChatGPT in your
6
+ browser, and Codex, and whatever you switch to next month.
7
+
8
+ ```bash
9
+ npx hivemind-cli init
10
+ ```
11
+
12
+ That opens your browser, signs you in, finds every AI client installed on the
13
+ machine, and writes each one's config in its own dialect. There is no key to
14
+ copy and nothing to paste.
15
+
16
+ ## What it configures
17
+
18
+ Claude Code · Claude Desktop · Cursor · VS Code · Windsurf · Zed · Cline ·
19
+ Roo Code · Codex CLI · Gemini CLI · Amp · OpenCode · Witsy
20
+
21
+ Each client gets its own credential, so revoking one machine leaves the rest
22
+ working.
23
+
24
+ ## Commands
25
+
26
+ ```bash
27
+ npx hivemind-cli init # sign in and configure everything detected
28
+ npx hivemind-cli init --dry-run # show what would change, write nothing
29
+ npx hivemind-cli status # which clients are connected, and to which account
30
+ npx hivemind-cli signout # remove Hivemind from every client
31
+ ```
32
+
33
+ | Flag | |
34
+ |---|---|
35
+ | `--with google\|github` | skip the provider chooser |
36
+ | `--key hm_live_...` | use an existing key instead of signing in |
37
+ | `--only cursor,vscode` | restrict to specific clients |
38
+ | `--url <endpoint>` | point at a different deployment |
39
+ | `--dry-run` | preview without writing |
40
+
41
+ ## What it does to your files
42
+
43
+ Configs are merged, never replaced — your other MCP servers and editor settings
44
+ are left alone. A backup is written next to each file the first time it is
45
+ touched (`<config>.hivemind-bak`).
46
+
47
+ Where a client owns its config at runtime and rewrites it as it goes — Claude
48
+ Code does this — the client's own CLI is used instead of editing the file
49
+ underneath it.
50
+
51
+ ## Why not just paste a key
52
+
53
+ Because a token in your shell history is a token in your shell history, and
54
+ because seven config files in five formats is not a thing anyone should do by
55
+ hand. `status` also tells you when a client is sitting on a *different* key
56
+ than the one you think, which is the failure that looks like a bug.
57
+
58
+ ## Links
59
+
60
+ - Dashboard and docs: <https://hivemind-api.hivemind-api.workers.dev>
61
+ - Source: <https://github.com/0xadityaa/hivemind>
62
+
63
+ MIT.
package/dist/index.js ADDED
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+ import{spawnSync as M}from"node:child_process";import{copyFileSync as a,existsSync as F,mkdirSync as i,readFileSync as N,writeFileSync as k}from"node:fs";import{dirname as r}from"node:path";import{homedir as u,platform as f}from"node:os";import{join as H}from"node:path";var B=u(),g=f()==="darwin",d=f()==="win32";function L(...z){if(g)return H(B,"Library","Application Support",...z);if(d)return H(process.env.APPDATA??H(B,"AppData","Roaming"),...z);return H(process.env.XDG_CONFIG_HOME??H(B,".config"),...z)}var T=(z)=>({Authorization:`Bearer ${z}`}),A=[{id:"claude-code",name:"Claude Code",configPath:H(B,".claude.json"),installHints:[H(B,".claude")],format:"json",serverMapKey:"mcpServers",entry:(z,Q)=>({type:"http",url:z,headers:T(Q)}),viaCommand:{bin:"claude",probeArgs:["--version"],args:(z,Q,X)=>["mcp","add","--scope","user","--transport","http",X,z,"--header",`Authorization: Bearer ${Q}`]}},{id:"claude-desktop",name:"Claude Desktop",configPath:L("Claude","claude_desktop_config.json"),installHints:["/Applications/Claude.app",L("Claude")],format:"json",serverMapKey:"mcpServers",entry:(z,Q)=>({type:"http",url:z,headers:T(Q)})},{id:"cursor",name:"Cursor",configPath:H(B,".cursor","mcp.json"),installHints:[H(B,".cursor"),"/Applications/Cursor.app"],format:"json",serverMapKey:"mcpServers",entry:(z,Q)=>({url:z,headers:T(Q)})},{id:"windsurf",name:"Windsurf",configPath:H(B,".codeium","windsurf","mcp_config.json"),installHints:[H(B,".codeium"),"/Applications/Windsurf.app"],format:"json",serverMapKey:"mcpServers",entry:(z,Q)=>({serverUrl:z,headers:T(Q)})},{id:"vscode",name:"VS Code",configPath:L("Code","User","mcp.json"),installHints:["/Applications/Visual Studio Code.app",L("Code")],format:"json",serverMapKey:"servers",entry:(z,Q)=>({type:"http",url:z,headers:T(Q)})},{id:"gemini-cli",name:"Gemini CLI",configPath:H(B,".gemini","settings.json"),installHints:[H(B,".gemini")],format:"json",serverMapKey:"mcpServers",entry:(z,Q)=>({httpUrl:z,headers:T(Q)})},{id:"zed",name:"Zed",configPath:H(B,".config","zed","settings.json"),installHints:[H(B,".config","zed"),"/Applications/Zed.app"],format:"json",serverMapKey:"context_servers",entry:(z,Q)=>({source:"custom",url:z,headers:T(Q)})},{id:"cline",name:"Cline",configPath:L("Code","User","globalStorage","saoudrizwan.claude-dev","settings","cline_mcp_settings.json"),installHints:[L("Code","User","globalStorage","saoudrizwan.claude-dev")],format:"json",serverMapKey:"mcpServers",entry:(z,Q)=>({type:"streamableHttp",url:z,headers:T(Q)})},{id:"roo",name:"Roo Code",configPath:L("Code","User","globalStorage","rooveterinaryinc.roo-cline","settings","mcp_settings.json"),installHints:[L("Code","User","globalStorage","rooveterinaryinc.roo-cline")],format:"json",serverMapKey:"mcpServers",entry:(z,Q)=>({type:"streamableHttp",url:z,headers:T(Q)})},{id:"witsy",name:"Witsy",configPath:L("Witsy","settings.json"),installHints:["/Applications/Witsy.app",L("Witsy")],format:"json",serverMapKey:"mcpServers",entry:(z,Q)=>({type:"http",url:z,headers:T(Q)})},{id:"amp",name:"Amp",configPath:L("amp","settings.json"),installHints:[L("amp"),H(B,".amp")],format:"json",serverMapKey:"amp.mcpServers",entry:(z,Q)=>({url:z,headers:T(Q)})},{id:"opencode",name:"OpenCode",configPath:H(B,".config","opencode","opencode.json"),installHints:[H(B,".config","opencode")],format:"json",serverMapKey:"mcp",entry:(z,Q)=>({type:"remote",url:z,enabled:!0,headers:T(Q)})},{id:"codex",name:"Codex CLI",configPath:H(B,".codex","config.toml"),installHints:[H(B,".codex")],format:"toml",serverMapKey:"mcp_servers",entry:(z)=>({url:z,bearer_token_env_var:"HIVEMIND_API_KEY"}),secretNote:"reads the key from $HIVEMIND_API_KEY — export it in your shell profile"}];function E(z,Q,X,$){let Z={},W=z.trim();if(W.length>0)try{let G=JSON.parse(W);if(G===null||typeof G!=="object"||Array.isArray(G))throw Error("config root is not an object");Z=G}catch(G){throw Error(`existing config is not valid JSON (${G instanceof Error?G.message:"parse error"})`)}let q=Z[Q],D=q&&typeof q==="object"&&!Array.isArray(q)?{...q}:{},J=Object.hasOwn(D,X);return D[X]=$,Z[Q]=D,{text:`${JSON.stringify(Z,null,2)}
3
+ `,replaced:J}}function x(z,Q,X){let $=`[mcp_servers.${Q}]`,Z=Object.entries(X).map(([G,O])=>`${G} = ${typeof O==="string"?JSON.stringify(O):String(O)}`).join(`
4
+ `),W=`${$}
5
+ ${Z}
6
+ `,q=$.replace(/[.[\]]/g,"\\$&"),D=new RegExp(`^${q}[^\\[]*`,"m");if(D.test(z))return{text:z.replace(D,W),replaced:!0};let J=z.length===0||z.endsWith(`
7
+
8
+ `)?"":z.endsWith(`
9
+ `)?`
10
+ `:`
11
+
12
+ `;return{text:`${z}${J}${W}`,replaced:!1}}import{createHash as p,randomBytes as o}from"node:crypto";import{createServer as l}from"node:http";import{spawn as c}from"node:child_process";import{hostname as n,platform as j}from"node:os";var s=900000;function h(z){return z.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function t(z){let Q=j()==="darwin"?"open":j()==="win32"?"cmd":"xdg-open",X=j()==="win32"?["/c","start","",z]:[z];try{c(Q,X,{stdio:"ignore",detached:!0}).unref()}catch{}}var b=(z,Q)=>`<!doctype html><meta charset="utf-8"><title>Hivemind</title>
13
+ <style>body{margin:0;min-height:100vh;display:grid;place-items:center;
14
+ font:15px/1.6 ui-sans-serif,system-ui;background:#0b0f14;color:#e6edf3}
15
+ .c{text-align:center;padding:40px 48px;border:1px solid #1f2933;border-radius:14px;background:#111820}
16
+ .t{width:44px;height:44px;border-radius:50%;background:#0f2e2b;color:#4fd1c5;
17
+ display:grid;place-items:center;margin:0 auto 16px;font-size:22px}
18
+ p{margin:6px 0;color:#8b98a5}</style>
19
+ <div class="c"><div class="t">&#10003;</div><strong>${z}</strong><p>${Q}</p></div>`;function S(z,Q,X){let $=h(o(48)),Z=h(p("sha256").update($).digest());return new Promise((W,q)=>{let D=l((C,P)=>{let I=new URL(C.url??"/","http://127.0.0.1");if(!I.pathname.startsWith("/callback")){P.writeHead(404).end();return}if(I.pathname==="/favicon.ico"){P.writeHead(204).end();return}let K=I.searchParams.get("code");if(!K){P.writeHead(400,{"content-type":"text/html"}),P.end(b("Sign-in failed","No authorization code was returned.")),O(Error("no code returned"));return}fetch(`${z}/auth/exchange`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({code:K,verifier:$,label:`cli@${n()}`.slice(0,60)})}).then(async(U)=>{let _=await U.json();if(!U.ok||!_.apiKey)throw Error(_.error??`exchange failed (${U.status})`);P.writeHead(200,{"content-type":"text/html"}),P.end(b("You're signed in","Return to your terminal — setup is continuing there.")),O(null,{apiKey:_.apiKey,email:_.email??null,userId:_.userId??""})}).catch((U)=>{P.writeHead(500,{"content-type":"text/html"}),P.end(b("Sign-in failed","Could not complete setup. Check your terminal.")),O(U)})}),J=!1,G=setTimeout(()=>O(Error("timed out waiting for sign-in")),s);function O(C,P){if(J)return;if(J=!0,clearTimeout(G),setTimeout(()=>{D.close(),D.closeAllConnections?.()},250),C)q(C);else W(P)}D.listen(0,"127.0.0.1",()=>{let C=D.address();if(C===null||typeof C==="string"){O(Error("could not open a local callback port"));return}let P=new URLSearchParams({challenge:Z,label:`loopback:${C.port}`}),I=Q?`${z}/auth/${Q}/start?${P}`:`${z}/signin?${P}`;X(I,`http://127.0.0.1:${C.port}/callback`),t(I)}),D.on("error",(C)=>O(C))})}var y="https://hivemind-api.hivemind-api.workers.dev/mcp",w="hivemind",Y={dim:(z)=>`\x1B[2m${z}\x1B[0m`,bold:(z)=>`\x1B[1m${z}\x1B[0m`,green:(z)=>`\x1B[32m${z}\x1B[0m`,yellow:(z)=>`\x1B[33m${z}\x1B[0m`,red:(z)=>`\x1B[31m${z}\x1B[0m`};function e(z){let X={command:z.some(($)=>$==="--help"||$==="-h")?"help":z[0]&&!z[0].startsWith("-")?z[0]:"init",url:process.env.HIVEMIND_URL??y,key:process.env.HIVEMIND_API_KEY,dryRun:!1};for(let $=0;$<z.length;$++){let Z=z[$];if(Z==="--key")X.key=z[++$];else if(Z==="--url")X.url=z[++$]??X.url;else if(Z==="--dry-run"||Z==="-n")X.dryRun=!0;else if(Z==="--only")X.only=(z[++$]??"").split(",").filter(Boolean);else if(Z==="--with")X.provider=z[++$]}return X}function R(z){if(F(z.configPath))return!0;return z.installHints.some((Q)=>F(Q))}function m(z){if(!z.viaCommand)return!1;return M(z.viaCommand.bin,z.viaCommand.probeArgs,{stdio:"ignore"}).status===0}function zz(z,Q,X,$){let Z=z.viaCommand;if($)return{client:z,status:"configured",detail:`dry run (${Z.bin})`};M(Z.bin,["mcp","remove","--scope","user",w],{stdio:"ignore"});let W=M(Z.bin,Z.args(Q,X,w),{stdio:["ignore","pipe","pipe"],encoding:"utf8"});if(W.status!==0)return{client:z,status:"failed",detail:(W.stderr||W.stdout||`${Z.bin} exited ${W.status}`).trim().split(`
20
+ `)[0]};return{client:z,status:"configured",detail:`via ${Z.bin}`}}function Qz(z,Q,X,$){if(m(z))return zz(z,Q,X,$);try{let Z=F(z.configPath)?N(z.configPath,"utf8"):"",W=z.entry(Q,X),q=z.format==="toml"?x(Z,w,W):E(Z,z.serverMapKey,w,W);if($)return{client:z,status:q.replaced?"updated":"configured",detail:"dry run"};if(i(r(z.configPath),{recursive:!0}),Z.length>0&&!F(`${z.configPath}.hivemind-bak`))a(z.configPath,`${z.configPath}.hivemind-bak`);return k(z.configPath,q.text,"utf8"),{client:z,status:q.replaced?"updated":"configured"}}catch(Z){return{client:z,status:"failed",detail:Z instanceof Error?Z.message:String(Z)}}}function Xz(z){return z.replace(/\/mcp\/?$/,"")}async function Yz(z){let Q=z.key;if(!Q){console.log(`
21
+ ${Y.bold("Hivemind")} ${Y.dim(z.url)}
22
+ `),console.log(` Opening your browser to sign in…
23
+ `);try{let J=await S(Xz(z.url),z.provider??null,(G,O)=>{console.log(` ${Y.dim("If it did not open:")} ${G}
24
+ `),console.log(` ${Y.dim(`Waiting for the browser to return to ${O}`)}`),console.log(` ${Y.dim("This window must stay open. Ctrl-C to cancel.")}
25
+ `)});Q=J.apiKey,console.log(` ${Y.green("✓")} Signed in${J.email?` as ${Y.bold(J.email)}`:""}
26
+ `)}catch(J){let G=J instanceof Error?J.message:"sign-in failed";if(console.error(` ${Y.red("×")} ${G}
27
+ `),G.includes("timed out"))console.error(` ${Y.dim("The browser did not come back in time. If you saw a")}
28
+ ${Y.dim('"connection refused" page, this window had already closed —')}
29
+ ${Y.dim("run init again and finish signing in without leaving it.")}
30
+ `);return console.error(` ${Y.dim("Or use a key from the dashboard:")} hivemind init --key hm_live_...
31
+ `),1}}let X=A.filter((J)=>!z.only||z.only.includes(J.id)),$=X.filter(R);if($.length===0)return console.log(` No supported AI clients detected.
32
+ `),console.log(Y.dim(` Looked for: ${X.map((J)=>J.name).join(", ")}
33
+ `)),1;let Z=$.map((J)=>Qz(J,z.url,Q,z.dryRun));for(let J of Z){let G=J.status==="failed"?Y.red("×"):J.status==="updated"?Y.yellow("↻"):Y.green("✓"),O=J.status==="updated"?"updated":J.status,C=J.status!=="failed"&&J.detail?Y.dim(` ${J.detail}`):"";if(console.log(` ${G} ${J.client.name.padEnd(16)} ${Y.dim(O)}${C}`),J.detail&&J.status==="failed")console.log(` ${Y.red(J.detail)}`);if(J.client.secretNote)console.log(` ${Y.dim(J.client.secretNote)}`)}let W=X.filter((J)=>!$.includes(J));if(W.length>0)console.log(`
34
+ ${Y.dim(`not installed: ${W.map((J)=>J.name).join(", ")}`)}`);let q=Z.filter((J)=>J.status==="failed").length,D=Z.length-q;if(z.dryRun)return console.log(`
35
+ ${Y.dim(`dry run — ${D} client(s) would be configured`)}
36
+ `),0;return console.log(`
37
+ ${D} client(s) configured${q>0?Y.red(`, ${q} failed`):""}.
38
+ ${Y.dim("Restart them to pick up the change.")}
39
+ `),q>0?1:0}async function Zz(z,Q){if(!Q)return null;try{let X=await fetch(`${z.replace(/\/mcp$/,"")}/v3/me`,{headers:{Authorization:`Bearer ${Q}`}});if(!X.ok)return null;return(await X.json()).userId??null}catch{return null}}function v(z){return/hm_live_([A-Za-z0-9]{12})_/.exec(z)?.[1]??null}async function $z(z){console.log(`
40
+ ${Y.bold("Hivemind")} — client status
41
+ `);let Q=await Zz(z.url,z.key);if(Q)console.log(` ${Y.dim(`account ${Q.slice(0,8)}`)}
42
+ `);for(let X of A){if(!R(X)){console.log(` ${Y.dim("–")} ${X.name.padEnd(16)} ${Y.dim("not installed")}`);continue}let $=!1,Z=null;try{let D=F(X.configPath)?N(X.configPath,"utf8"):"",J=X.format==="toml"?D.includes(`[mcp_servers.${w}]`)?D:"":JSON.stringify(JSON.parse(D||"{}")[X.serverMapKey]?.[w]??"");if($=Boolean(J&&J!=='""'),$)Z=v(J)}catch{$=!1}let W=Z&&z.key&&Z!==v(z.key),q=!$?"installed, not configured":W?`connected — DIFFERENT key (${Z})`:"connected";console.log(` ${$?W?Y.red("!"):Y.green("✓"):Y.yellow("○")} ${X.name.padEnd(16)} ${W?Y.red(q):Y.dim(q)}`)}return console.log(),0}function Jz(z){console.log(`
43
+ ${Y.bold("Hivemind")} — removing from clients
44
+ `);let Q=0;for(let X of A){if(!R(X))continue;if(z.dryRun){if(F(X.configPath)&&N(X.configPath,"utf8").includes(w))console.log(` ${Y.yellow("○")} ${X.name.padEnd(16)} ${Y.dim("would be removed")}`),Q++;continue}if(m(X)){let $=X.viaCommand;if(M($.bin,["mcp","remove","--scope","user",w],{stdio:"ignore"}).status===0){console.log(` ${Y.green("✓")} ${X.name.padEnd(16)} ${Y.dim(`removed via ${$.bin}`)}`),Q++;continue}}if(!F(X.configPath))continue;try{let $=N(X.configPath,"utf8");if(X.format==="toml"){let Z=$.replace(new RegExp(`\\[mcp_servers\\.${w}\\][^\\[]*`,"g"),"");if(Z===$)continue;k(X.configPath,Z.trimStart(),"utf8")}else{let Z=JSON.parse($||"{}"),W=Z[X.serverMapKey];if(!W||!(w in W))continue;delete W[w],k(X.configPath,`${JSON.stringify(Z,null,2)}
45
+ `,"utf8")}console.log(` ${Y.green("✓")} ${X.name.padEnd(16)} ${Y.dim("removed")}`),Q++}catch($){console.log(` ${Y.red("×")} ${X.name.padEnd(16)} `+Y.red($ instanceof Error?$.message:"could not edit config"))}}if(z.dryRun)return console.log(`
46
+ ${Y.dim(`dry run — ${Q} client(s) would be changed`)}
47
+ `),0;if(console.log(Q===0?`
48
+ ${Y.dim("Nothing to remove.")}
49
+ `:`
50
+ Removed from ${Q} client(s). ${Y.dim("Restart them to pick up the change.")}
51
+ `),z.key)console.log(` ${Y.dim("Your API key is still valid — revoke it in the dashboard to end access.")}
52
+ `);return 0}function Wz(){return console.log(`
53
+ ${Y.bold("hivemind")} — one memory across every AI tool
54
+
55
+ ${Y.bold("init")} sign in and configure all detected clients
56
+ ${Y.bold("status")} show which clients are connected
57
+ ${Y.bold("signout")} remove Hivemind from every configured client
58
+
59
+ --with <p> sign in with a provider directly: google, github
60
+ --key <k> use an existing API key instead of signing in
61
+ --url <u> MCP endpoint (default: ${y})
62
+ --only a,b restrict to specific client ids
63
+ --dry-run show what would change, write nothing
64
+
65
+ ${Y.dim(`clients: ${A.map((z)=>z.id).join(", ")}`)}
66
+ `),0}var V=e(process.argv.slice(2)),qz=V.command==="init"?await Yz(V):V.command==="status"?await $z(V):V.command==="signout"?Jz(V):Wz();process.exit(qz);
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "hivemind-cli",
3
+ "version": "0.1.0",
4
+ "description": "One memory across every AI tool. Signs in through your browser and configures every MCP client on your machine.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "bin": {
8
+ "hivemind": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md"
13
+ ],
14
+ "engines": {
15
+ "node": ">=18"
16
+ },
17
+ "keywords": [
18
+ "mcp",
19
+ "model-context-protocol",
20
+ "ai",
21
+ "memory",
22
+ "context",
23
+ "claude",
24
+ "cursor",
25
+ "llm"
26
+ ],
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/0xadityaa/hivemind.git",
30
+ "directory": "cli"
31
+ },
32
+ "scripts": {
33
+ "build": "bun build src/index.ts --outdir dist --target node --format esm --minify && chmod +x dist/index.js",
34
+ "prepublishOnly": "bun run build",
35
+ "test": "bun test"
36
+ }
37
+ }