@claudecollab/cli 0.1.1 → 0.1.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 +55 -68
- package/package.json +1 -1
- package/packages/cli/src/pty.js +38 -0
package/README.md
CHANGED
|
@@ -1,124 +1,111 @@
|
|
|
1
1
|
# ✦ claudecollab
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Use Claude Code with your friends.** You run one command. Your friends open a link and can collaborate with the same Claude as you.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
```mermaid
|
|
10
|
-
flowchart LR
|
|
11
|
-
H["you (host)<br/>terminal runs Claude"] <--> R["relay<br/>forwards bytes, nothing to disk"]
|
|
12
|
-
R <--> HT["your host tab<br/>(admit · roles · pause · end)"]
|
|
13
|
-
R <--> G["friends' browsers"]
|
|
5
|
+
```
|
|
6
|
+
You a helper server Your friends
|
|
7
|
+
(you run Claude) <--> (passes messages) <--> (open a link)
|
|
14
8
|
```
|
|
15
9
|
|
|
16
|
-
Your terminal
|
|
10
|
+
Your terminal looks like normal Claude, just one extra line at the bottom. All the sharing happens in the web browser.
|
|
17
11
|
|
|
18
|
-
##
|
|
12
|
+
## Start a session
|
|
19
13
|
|
|
20
14
|
```bash
|
|
21
15
|
npm install -g @claudecollab/cli
|
|
22
16
|
collab
|
|
23
17
|
```
|
|
24
18
|
|
|
25
|
-
That's it. `collab`
|
|
19
|
+
That's it. Run `collab` instead of `claude`. It works the same, but now it makes two links:
|
|
20
|
+
|
|
21
|
+
- A **private link** — that's your control panel (you let people in and manage them).
|
|
22
|
+
- A **share link** — this one gets copied for you. Send it to your friends.
|
|
23
|
+
|
|
24
|
+
Your friends just open the share link in a browser. They don't install anything.
|
|
26
25
|
|
|
27
26
|
## Two links — don't mix them up
|
|
28
27
|
|
|
29
28
|
| Link | Who it's for |
|
|
30
29
|
|---|---|
|
|
31
|
-
|
|
|
32
|
-
|
|
|
30
|
+
| the one ending in `?host=…` | **Just you.** Opening it makes you the boss of the room. |
|
|
31
|
+
| the plain one | **Share this.** Friends use it to ask to join. |
|
|
33
32
|
|
|
34
|
-
The Invite button
|
|
33
|
+
The Invite button adds the plain one to your clipboard.
|
|
35
34
|
|
|
36
|
-
Want
|
|
35
|
+
Want an extra guardrail? Start with `collab --room-password <pw>`. Then friends have to type the password before they can even ask to join. You still choose who gets admitted.
|
|
37
36
|
|
|
38
37
|
## Roles
|
|
39
38
|
|
|
40
|
-
| Role |
|
|
39
|
+
| Role | Can watch | Can type | Can run the room |
|
|
41
40
|
|---|---|---|---|
|
|
42
|
-
| 👁 viewer |
|
|
43
|
-
| ✎ prompter *(
|
|
44
|
-
| ★ host |
|
|
41
|
+
| 👁 viewer | yes | no | no |
|
|
42
|
+
| ✎ prompter *(normal)* | yes | yes | no |
|
|
43
|
+
| ★ host *(you)* | yes | yes | yes |
|
|
44
|
+
|
|
45
|
+
"Run the room" means let people in, remove them, pause, or end it.
|
|
45
46
|
|
|
46
|
-
## How it
|
|
47
|
+
## How it works
|
|
47
48
|
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
- Reloaded guests glide straight back in. Kicked guests lose the room; a room password (below) is the real lock if you need one.
|
|
49
|
+
- Everyone who can type gets their own little box at the bottom to write in. Type there and press Enter to send. Your box stays open, so you can keep going. If Claude is busy, your message will get queued.
|
|
50
|
+
- Want to write a message *together* with someone? Click **+ draft** to open a shared box you both type in.
|
|
51
|
+
- To talk to Claude directly (to approve y/n questions, answer questions, etc) click Claude's CLI text line.
|
|
52
|
+
- Everyone sees the same screen, and scrolling is shared.
|
|
53
53
|
|
|
54
|
-
##
|
|
54
|
+
## Important note
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
When a friend types something, **it runs on your computer, as if you typed it.** So only let in people you trust. You can hit **Pause** anytime, or **End** to close the room.
|
|
57
57
|
|
|
58
|
-
##
|
|
58
|
+
## Run your own server (optional)
|
|
59
59
|
|
|
60
|
-
`collab`
|
|
60
|
+
By default, `collab` uses our free server at claudecollab.org. Want to run your own instead? The same install includes it:
|
|
61
61
|
|
|
62
62
|
```bash
|
|
63
|
-
collab-relay #
|
|
64
|
-
collab --relay ssh://127.0.0.1:2222 #
|
|
63
|
+
collab-relay # start your own server
|
|
64
|
+
collab --relay ssh://127.0.0.1:2222 # use it
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
To put your server online for good (on Fly.io or any host — see `fly.toml`):
|
|
68
68
|
|
|
69
69
|
```bash
|
|
70
70
|
fly launch --no-deploy
|
|
71
71
|
fly secrets set HOST_KEY="$(collab-relay --make-key)"
|
|
72
|
-
fly secrets set ROOM_SECRET="$(openssl rand -hex 16)" # optional
|
|
72
|
+
fly secrets set ROOM_SECRET="$(openssl rand -hex 16)" # optional lock (see below)
|
|
73
73
|
fly deploy
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
-
Two
|
|
76
|
+
Two safety features come built in:
|
|
77
77
|
|
|
78
|
-
- **Room
|
|
79
|
-
- **
|
|
78
|
+
- **Room password** (`ROOM_SECRET`) — if you set this, only people who know it can *start* rooms on your server. People *joining* a room don't need it.
|
|
79
|
+
- **Server ID check** the first time you connect, the app remembers your server's ID. If it ever changes, it stops and warns you, so nobody can pretend to be your server.
|
|
80
80
|
|
|
81
|
-
##
|
|
81
|
+
## All the options
|
|
82
82
|
|
|
83
|
-
|
|
|
83
|
+
| Option | What it does |
|
|
84
84
|
|---|---|
|
|
85
|
-
| `--relay <url>` |
|
|
86
|
-
| `--no-relay` | solo
|
|
87
|
-
| `--room-password <pw>` |
|
|
88
|
-
| `--guests <role>` |
|
|
89
|
-
| `--secret <s>` |
|
|
90
|
-
| `--fingerprint <fp>` |
|
|
91
|
-
| `--cmd <program>` |
|
|
92
|
-
| `-- <args…>` |
|
|
93
|
-
|
|
94
|
-
## Develop
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
npm install
|
|
98
|
-
npm test # full suite: protocol, relay, brain, e2e
|
|
99
|
-
scripts/rig.sh # local relay + host wrapping a fake claude (UI work)
|
|
100
|
-
```
|
|
85
|
+
| `--relay <url>` | which server to use; defaults to ours |
|
|
86
|
+
| `--no-relay` | go solo — just Claude, no room, no sharing |
|
|
87
|
+
| `--room-password <pw>` | friends must type this before they can ask to join |
|
|
88
|
+
| `--guests <role>` | what new people can do when let in (default: prompter) |
|
|
89
|
+
| `--secret <s>` | the password for a locked server (or use `CLAUDE_SHARE_SECRET`) |
|
|
90
|
+
| `--fingerprint <fp>` | lock onto a specific server's ID |
|
|
91
|
+
| `--cmd <program>` | run something other than `claude` |
|
|
92
|
+
| `-- <args…>` | anything after `--` is passed to the program you're running |
|
|
101
93
|
|
|
102
94
|
<details>
|
|
103
|
-
<summary>
|
|
95
|
+
<summary>How it's built</summary>
|
|
104
96
|
|
|
105
|
-
Three
|
|
97
|
+
Three parts, plain JavaScript, no build step:
|
|
106
98
|
|
|
107
99
|
```
|
|
108
|
-
packages/shared/
|
|
109
|
-
packages/relay/ server
|
|
110
|
-
packages/cli/
|
|
111
|
-
test/ end-to-end: host + host tab + guest on localhost
|
|
100
|
+
packages/shared/ the messages the app and server send each other
|
|
101
|
+
packages/relay/ the server (one door for terminals, one for browsers)
|
|
102
|
+
packages/cli/ the main program you run — the brain
|
|
112
103
|
```
|
|
113
104
|
|
|
114
|
-
- The CLI wraps Claude in a PTY one row short — the bottom row is the status line.
|
|
115
|
-
- State (busy/idle/ask) comes from injected Claude Code hooks, never screen-scraping. Ambiguity fails closed.
|
|
116
|
-
- The relay persists nothing to disk — rooms, names, and any room password live only in process memory (kill it and the host reconnects; kill the host and the room is over). It does relay your screen bytes in transit, so treat it like a screen-share, not a vault — or self-host your own.
|
|
117
|
-
|
|
118
105
|
</details>
|
|
119
106
|
|
|
120
107
|
## License
|
|
121
108
|
|
|
122
|
-
[MIT](LICENSE)
|
|
109
|
+
[MIT](LICENSE) — free to use and change. This is an independent project, not made or approved by Anthropic.
|
|
123
110
|
|
|
124
|
-
|
|
111
|
+
Contributions are welcome! Please open a PR and add @ir272 as a reviewer.
|
package/package.json
CHANGED
package/packages/cli/src/pty.js
CHANGED
|
@@ -10,8 +10,45 @@
|
|
|
10
10
|
// node-pty is a native module and is imported lazily inside startPty, so this
|
|
11
11
|
// file (and FrameSplitter's tests) load fine even where node-pty isn't built.
|
|
12
12
|
|
|
13
|
+
import fs from 'node:fs';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { createRequire } from 'node:module';
|
|
16
|
+
|
|
13
17
|
/** Synchronized-update END marker. Claude ends every repaint frame with this. */
|
|
14
18
|
export const SU_END = '\x1b[?2026l';
|
|
19
|
+
|
|
20
|
+
// node-pty forks a small `spawn-helper` binary to launch the child, and that file
|
|
21
|
+
// must be executable. Installers that skip package build scripts (npm's
|
|
22
|
+
// allow-scripts / ignore-scripts policies, pnpm by default) ship it WITHOUT the
|
|
23
|
+
// execute bit, so the very first spawn dies with "posix_spawnp failed". A
|
|
24
|
+
// postinstall script can't fix this — those same policies block it — so we restore
|
|
25
|
+
// the bit at RUNTIME, right before spawning. Best-effort and idempotent: if the
|
|
26
|
+
// bit is already set (or we can't touch the file) we do nothing and let spawn
|
|
27
|
+
// surface the real error. macOS/Linux only; Windows has no such helper.
|
|
28
|
+
export function ensureSpawnHelperExecutable(requireFn) {
|
|
29
|
+
if (process.platform === 'win32') return;
|
|
30
|
+
try {
|
|
31
|
+
const req = requireFn ?? createRequire(import.meta.url);
|
|
32
|
+
const ptyDir = path.dirname(req.resolve('node-pty/package.json'));
|
|
33
|
+
const candidates = [path.join(ptyDir, 'build', 'Release', 'spawn-helper')];
|
|
34
|
+
const prebuilds = path.join(ptyDir, 'prebuilds');
|
|
35
|
+
try {
|
|
36
|
+
for (const d of fs.readdirSync(prebuilds)) candidates.push(path.join(prebuilds, d, 'spawn-helper'));
|
|
37
|
+
} catch {
|
|
38
|
+
/* no prebuilds dir on this layout */
|
|
39
|
+
}
|
|
40
|
+
for (const f of candidates) {
|
|
41
|
+
try {
|
|
42
|
+
const { mode } = fs.statSync(f);
|
|
43
|
+
if (!(mode & 0o111)) fs.chmodSync(f, mode | 0o111); // add u+g+o execute
|
|
44
|
+
} catch {
|
|
45
|
+
/* not present here, or not ours to chmod — skip */
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
/* couldn't even resolve node-pty — the import below throws the real error */
|
|
50
|
+
}
|
|
51
|
+
}
|
|
15
52
|
/** Synchronized-update BEGIN marker (exported for reference / future use). */
|
|
16
53
|
export const SU_BEGIN = '\x1b[?2026h';
|
|
17
54
|
|
|
@@ -78,6 +115,7 @@ export async function startPty({
|
|
|
78
115
|
cwd,
|
|
79
116
|
flushMs = 8,
|
|
80
117
|
} = {}) {
|
|
118
|
+
ensureSpawnHelperExecutable(); // fix node-pty's spawn-helper exec bit before spawning
|
|
81
119
|
const mod = await import('node-pty');
|
|
82
120
|
const spawn = mod.spawn ?? mod.default?.spawn;
|
|
83
121
|
if (typeof spawn !== 'function') {
|