collide-cli 0.0.0-stage → 1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lithometric
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,30 @@
1
- # Temporary Holding Version
1
+ # collide-cli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Your coding agents, working as a team. One command installs Collide for every repo on your machine: Claude Code, Codex and Cursor agents in the same repo see each other's changes as they happen, get briefed on the code they are about to touch, and can message each other.
4
+
5
+ ```sh
6
+ npx collide-cli
7
+ ```
8
+
9
+ Free, no account, and nothing leaves your machine. Works on macOS, Linux and Windows (PowerShell, cmd or Git Bash).
10
+
11
+ It downloads the `collide` program for your platform, checks it against its SHA-256 and Collide's release signature before running it, installs it into `~/.collide/bin`, connects your agent tools, and adds `collide` to your PATH.
12
+
13
+ ## Commands
14
+
15
+ ```sh
16
+ npx collide-cli # install or update
17
+ npx collide-cli status # what Collide did on this machine
18
+ npx collide-cli login # link this machine to a Collide account
19
+ npx collide-cli upgrade # bring in your team
20
+ ```
21
+
22
+ After install, `collide <command>` works directly in a new terminal.
23
+
24
+ This package was called `collidemcp` until v1.1.0. It still answers as `collidemcp`, and the old package forwards here, so `npx collidemcp` keeps working.
25
+
26
+ No Node? `curl -fsSL https://collidemcp.com/install.sh | sh`, or with Python, `pipx run collide-sdk install`.
27
+
28
+ Is the service up? On a machine signed in to an account, `collide status` adds one line from [collidemcp.com/status](https://collidemcp.com/status) (`Collide service: operational`, or what is not); agents can read [status.json](https://collidemcp.com/status.json).
29
+
30
+ More at [collidemcp.com](https://collidemcp.com).
@@ -0,0 +1,215 @@
1
+ #!/usr/bin/env node
2
+ // npx collide-cli: Collide for every repo on this machine, in one command.
3
+ // (It also answers as `collidemcp`, the package's former name.)
4
+ //
5
+ // Downloads the collide program for this platform from Collide's releases,
6
+ // checks it against its SHA-256 and CI's Ed25519 signature before it runs,
7
+ // puts it in ~/.collide/bin, sets it up for Claude Code, Codex and Cursor,
8
+ // and adds it to PATH. The same program the install script installs.
9
+ //
10
+ // npx collide-cli install (or update) Collide on this machine
11
+ // npx collide-cli <command> run a collide command: login, status, upgrade, ...
12
+ // npx collide-cli --help what it does, without installing anything
13
+
14
+ "use strict";
15
+
16
+ const crypto = require("node:crypto");
17
+ const fs = require("node:fs");
18
+ const os = require("node:os");
19
+ const path = require("node:path");
20
+ const { spawnSync } = require("node:child_process");
21
+
22
+ const SERVER = (process.env.COLLIDE_INSTALL_SERVER || "https://mcp.collidemcp.com").replace(/\/+$/, "");
23
+ // CI's release-signing key; the program checks its own updates against the same one
24
+ const SIGNING_KEY = "17092e6d5632783f1d9b08fc134b0343c7e67b4a4b42feb52df6ca295f325ef8";
25
+
26
+ const color = process.stdout.isTTY && !process.env.NO_COLOR;
27
+ const paint = (code) => (text) => (color ? `\x1b[${code}m${text}\x1b[0m` : text);
28
+ const mint = paint("38;2;52;211;153");
29
+ const dim = paint("2");
30
+ const bold = paint("1");
31
+
32
+ function fail(message) {
33
+ console.error(`Collide: ${message}`);
34
+ process.exit(1);
35
+ }
36
+
37
+ function target() {
38
+ const arch = { arm64: "aarch64", x64: "x86_64" }[process.arch];
39
+ const system = { darwin: "apple-darwin", linux: "unknown-linux-gnu", win32: "pc-windows-msvc" }[process.platform];
40
+ if (!arch || !system) fail(`there is no build for ${process.platform}-${process.arch} yet.`);
41
+ return `${arch}-${system}`;
42
+ }
43
+
44
+ function verifySignature(statement, signature) {
45
+ const der = Buffer.concat([Buffer.from("302a300506032b6570032100", "hex"), Buffer.from(SIGNING_KEY, "hex")]);
46
+ const key = crypto.createPublicKey({ key: der, format: "der", type: "spki" });
47
+ return crypto.verify(null, Buffer.from(statement), key, Buffer.from(signature, "base64"));
48
+ }
49
+
50
+ function banner(version) {
51
+ if (!process.stdout.isTTY) return;
52
+ const art = [
53
+ " ██████╗ ██████╗ ██╗ ██╗ ██╗██████╗ ███████╗",
54
+ "██╔════╝██╔═══██╗██║ ██║ ██║██╔══██╗██╔════╝",
55
+ "██║ ██║ ██║██║ ██║ ██║██║ ██║█████╗ ",
56
+ "██║ ██║ ██║██║ ██║ ██║██║ ██║██╔══╝ ",
57
+ "╚██████╗╚██████╔╝███████╗███████╗██║██████╔╝███████╗",
58
+ " ╚═════╝ ╚═════╝ ╚══════╝╚══════╝╚═╝╚═════╝ ╚══════╝",
59
+ ];
60
+ console.log("\n" + art.map(mint).join("\n"));
61
+ console.log(` ${dim(`free · v${version} · your agents, working as a team`)}\n`);
62
+ }
63
+
64
+ /** `collide` by name in every new terminal: ~/.collide/bin joins PATH once. */
65
+ function addToPath(binDir) {
66
+ if (process.env.COLLIDE_NO_MODIFY_PATH === "1") return "";
67
+ const home = os.homedir();
68
+ if (process.platform === "win32") {
69
+ // the user's own PATH, read and written through PowerShell (setx truncates long values)
70
+ const script =
71
+ `$p=[Environment]::GetEnvironmentVariable('Path','User'); if(-not $p){$p=''}; ` +
72
+ `if(($p -split ';') -notcontains '${binDir}'){[Environment]::SetEnvironmentVariable('Path',($p.TrimEnd(';')+';${binDir}').TrimStart(';'),'User'); 'added'}`;
73
+ const out = spawnSync("powershell", ["-NoProfile", "-Command", script], { encoding: "utf8" });
74
+ return out.stdout && out.stdout.includes("added") ? "your user PATH" : "";
75
+ }
76
+ const shell = process.env.SHELL || "";
77
+ const files = shell.endsWith("/zsh")
78
+ ? [".zshrc"]
79
+ : shell.endsWith("/bash")
80
+ ? process.platform === "darwin" ? [".bashrc", ".bash_profile"] : [".bashrc"]
81
+ : shell.endsWith("/fish")
82
+ ? [".config/fish/conf.d/collide.fish"]
83
+ : [".profile"];
84
+ const added = [];
85
+ for (const name of files) {
86
+ const file = path.join(home, name);
87
+ let current = "";
88
+ try {
89
+ current = fs.readFileSync(file, "utf8");
90
+ } catch {}
91
+ if (current.includes(".collide/bin")) continue;
92
+ fs.mkdirSync(path.dirname(file), { recursive: true });
93
+ fs.appendFileSync(
94
+ file,
95
+ name.endsWith(".fish") ? 'fish_add_path "$HOME/.collide/bin"\n' : '\n# Collide\nexport PATH="$HOME/.collide/bin:$PATH"\n',
96
+ );
97
+ added.push(`~/${name}`);
98
+ }
99
+ return added.join(", ");
100
+ }
101
+
102
+ async function install(args) {
103
+ const triple = target();
104
+ let index;
105
+ try {
106
+ const response = await fetch(`${SERVER}/artifacts/hooks`, { headers: { "user-agent": "collide-cli-npm" } });
107
+ if (!response.ok) throw new Error(String(response.status));
108
+ index = await response.json();
109
+ } catch (err) {
110
+ fail(`could not reach Collide's releases (${err.message}). Check your connection and run it again.`);
111
+ }
112
+ // a release whose builds are still on their way: the version before,
113
+ // which every installed copy then updates from by itself
114
+ let version = index.version;
115
+ let build = (index.free_builds || []).find((b) => b.target === triple);
116
+ if (!build && index.previous) {
117
+ build = (index.previous.free_builds || []).find((b) => b.target === triple);
118
+ if (build) version = index.previous.version;
119
+ }
120
+ if (!build) fail(`there is no build for ${triple} in release v${index.version} yet; try again in a few minutes.`);
121
+ banner(version);
122
+
123
+ console.log(` ${dim(`Downloading collide for ${triple}...`)}`);
124
+ const response = await fetch(build.url, { headers: { "user-agent": "collide-cli-npm" } });
125
+ if (!response.ok) fail(`the download failed (${response.status}). Run it again.`);
126
+ const bytes = Buffer.from(await response.arrayBuffer());
127
+ const digest = crypto.createHash("sha256").update(bytes).digest("hex");
128
+ if (digest !== build.sha256) fail("the download did not match its checksum, so nothing was installed.");
129
+ if (!build.sig || !verifySignature(`collide ${triple} v${version} sha256:${digest}`, build.sig)) {
130
+ fail("the download is not signed by Collide's release key, so nothing was installed.");
131
+ }
132
+ console.log(` ${mint("✓")} Downloaded and verified ${dim("(SHA-256 and signature)")}`);
133
+
134
+ const binDir = path.join(os.homedir(), ".collide", "bin");
135
+ fs.mkdirSync(binDir, { recursive: true });
136
+ const program = path.join(binDir, process.platform === "win32" ? "collide.exe" : "collide");
137
+ const staged = `${program}.part`;
138
+ fs.writeFileSync(staged, bytes, { mode: 0o755 });
139
+ // a running copy on Windows cannot be replaced, only renamed aside
140
+ if (fs.existsSync(program)) {
141
+ try {
142
+ fs.renameSync(program, `${program}.old`);
143
+ } catch {}
144
+ }
145
+ fs.renameSync(staged, program);
146
+
147
+ const noLogin = args.includes("--no-login") || process.env.COLLIDE_NO_LOGIN === "1";
148
+ const passed = args.filter((a) => a !== "--no-login");
149
+ const setup = spawnSync(program, ["install", ...passed], { stdio: "inherit", env: { ...process.env, COLLIDE_STYLE: "1" } });
150
+ if (setup.status !== 0) process.exit(setup.status || 1);
151
+ const added = addToPath(binDir);
152
+ if (added) console.log(` ${mint("✓")} Added to your PATH ${dim(`(${added})`)}`);
153
+
154
+ // one sign-in, in the browser, as part of the install: the dashboard opens
155
+ // there, and this terminal comes back to the front when it is done
156
+ if (!noLogin && process.stdin.isTTY && process.stdout.isTTY && !linked()) {
157
+ console.log(`\n ${bold("Sign in")} ${dim("(opens your browser · free · links this machine to your dashboard)")}`);
158
+ const login = spawnSync(program, ["login"], { stdio: "inherit" });
159
+ focusTerminal();
160
+ if (login.status !== 0) console.log(` ${dim("Not signed in. Any time:")} ${mint("collide login")}`);
161
+ }
162
+
163
+ console.log(`\n ${bold("Next")}`);
164
+ console.log(` ${mint("1")} Open a new terminal, so the ${bold("collide")} command works`);
165
+ console.log(` ${mint("2")} Start or restart your agent sessions: agents in the same repo now see`);
166
+ console.log(" each other's work and can message each other");
167
+ console.log(`\n ${dim("Bring in your team:")} ${mint("collide upgrade")} ${dim("($10/seat, 14 days free)")}\n`);
168
+ }
169
+
170
+ /** This machine already signed in (`collide login` keeps the account in
171
+ * machine.json): the install leaves it as it is. */
172
+ function linked() {
173
+ try {
174
+ return Boolean(JSON.parse(fs.readFileSync(path.join(os.homedir(), ".collide", "machine.json"), "utf8")).account);
175
+ } catch {
176
+ return false;
177
+ }
178
+ }
179
+
180
+ /** Back to the app the install was run from, once the browser is done with
181
+ * the sign-in. macOS names that app in __CFBundleIdentifier (Terminal,
182
+ * iTerm, Ghostty, VS Code, Cursor...); elsewhere the person switches back. */
183
+ function focusTerminal() {
184
+ const app = process.env.__CFBundleIdentifier;
185
+ if (process.platform === "darwin" && app && /^[\w.-]+$/.test(app)) spawnSync("open", ["-b", app]);
186
+ }
187
+
188
+ function run(args) {
189
+ const program = path.join(os.homedir(), ".collide", "bin", process.platform === "win32" ? "collide.exe" : "collide");
190
+ if (!fs.existsSync(program)) return install([]).then(() => run(args));
191
+ const result = spawnSync(program, args, { stdio: "inherit" });
192
+ process.exit(result.status ?? 1);
193
+ }
194
+
195
+ function help() {
196
+ const name = path.basename(process.argv[1] || "collide-cli").replace(/\.js$/, "");
197
+ console.log(`${bold("collide-cli")} ${dim(`v${require("../package.json").version}`)}: Collide for every repo on this machine.
198
+
199
+ ${mint(`npx ${name}`)} install or update Collide (checks SHA-256 and signature first)
200
+ ${mint(`npx ${name} --no-login`)} install without the browser sign-in
201
+ ${mint(`npx ${name} <command>`)} run a collide command: status, login, upgrade, ...
202
+ ${mint(`npx ${name} --help`)} this text
203
+
204
+ After the install, ${bold("collide <command>")} works directly in a new terminal.
205
+ More at https://collidemcp.com/docs/cli`);
206
+ }
207
+
208
+ const args = process.argv.slice(2);
209
+ if (args[0] === "--help" || args[0] === "-h") {
210
+ help();
211
+ } else if (args.length === 0 || args[0] === "install" || args[0].startsWith("--")) {
212
+ install(args[0] === "install" ? args.slice(1) : args).catch((err) => fail(err.message));
213
+ } else {
214
+ run(args);
215
+ }
package/package.json CHANGED
@@ -1,6 +1,33 @@
1
1
  {
2
2
  "name": "collide-cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.1.0",
4
+ "description": "Collide: your coding agents (Claude Code, Codex, Cursor) working as a team. One command installs it for every repo on your machine.",
5
+ "bin": {
6
+ "collide-cli": "bin/collide-cli.js",
7
+ "collidemcp": "bin/collide-cli.js"
8
+ },
9
+ "files": [
10
+ "bin/collide-cli.js",
11
+ "README.md",
12
+ "LICENSE"
13
+ ],
14
+ "engines": {
15
+ "node": ">=18"
16
+ },
17
+ "keywords": [
18
+ "claude-code",
19
+ "codex",
20
+ "cursor",
21
+ "mcp",
22
+ "ai-agents",
23
+ "multi-agent",
24
+ "collaboration"
25
+ ],
26
+ "homepage": "https://collidemcp.com",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/lithometric/collide"
30
+ },
31
+ "license": "MIT",
32
+ "author": "Lithometric"
33
+ }