claude-friends 0.2.1 → 0.3.1
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 +26 -34
- package/cli.js +118 -21
- package/commands/friend.md +1 -1
- package/commands/friends.md +1 -1
- package/commands/nudge.md +1 -1
- package/commands/status.md +1 -1
- package/commands/unfriend.md +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,55 +11,47 @@ See who's online in Claude Code. Add friends, share what you're working on, nudg
|
|
|
11
11
|
```bash
|
|
12
12
|
npm install -g claude-friends
|
|
13
13
|
claude-friends setup
|
|
14
|
-
claude mcp add claude-friends -- claude-friends serve
|
|
15
14
|
```
|
|
16
15
|
|
|
17
|
-
That's it. No database, no API keys.
|
|
16
|
+
That's it. No database, no API keys, no MCP server to configure.
|
|
18
17
|
|
|
19
|
-
##
|
|
18
|
+
## Usage
|
|
20
19
|
|
|
21
|
-
|
|
20
|
+
### Slash commands (inside Claude Code)
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
| "check nudges" | See if anyone poked you |
|
|
31
|
-
|
|
32
|
-
## Status line
|
|
22
|
+
```
|
|
23
|
+
/friend alice Add a friend
|
|
24
|
+
/friends See who's online
|
|
25
|
+
/nudge bob hey! Nudge someone
|
|
26
|
+
/status debugging Set your status
|
|
27
|
+
/unfriend alice Remove a friend
|
|
28
|
+
```
|
|
33
29
|
|
|
34
|
-
|
|
30
|
+
### CLI (from any terminal)
|
|
35
31
|
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
32
|
+
```bash
|
|
33
|
+
claude-friends add alice
|
|
34
|
+
claude-friends online
|
|
35
|
+
claude-friends nudge bob "ship it!"
|
|
36
|
+
claude-friends status "pair programming"
|
|
37
|
+
claude-friends remove alice
|
|
43
38
|
```
|
|
44
39
|
|
|
45
|
-
|
|
40
|
+
## Features
|
|
46
41
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
```
|
|
42
|
+
- **Online presence** — see who's in Claude Code right now
|
|
43
|
+
- **Status messages** — share what you're working on
|
|
44
|
+
- **Nudges** — poke a friend with a message
|
|
45
|
+
- **Token usage** — automatically shared with friends (opt-in via hook)
|
|
46
|
+
- **Status line** — friend count shown in Claude Code's bottom bar
|
|
55
47
|
|
|
56
48
|
## How it works
|
|
57
49
|
|
|
58
50
|
- **PartyKit** handles real-time presence via WebSockets
|
|
59
|
-
- When you
|
|
60
|
-
-
|
|
61
|
-
- Friend lists and nudges are stored in-memory on the server
|
|
51
|
+
- When you use Claude Code → you appear online
|
|
52
|
+
- Friend lists and nudges are stored on the server
|
|
62
53
|
- No accounts, no passwords — just a username
|
|
54
|
+
- Setup auto-installs slash commands, status line, and token-sharing hook
|
|
63
55
|
|
|
64
56
|
## Self-hosting
|
|
65
57
|
|
package/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import { writeFileSync, readFileSync, existsSync, mkdirSync, copyFileSync, readd
|
|
|
4
4
|
import { join } from "path";
|
|
5
5
|
import { homedir } from "os";
|
|
6
6
|
import { createInterface } from "readline";
|
|
7
|
-
import { getConfig } from "./client.js";
|
|
7
|
+
import { getConfig, createConnection } from "./client.js";
|
|
8
8
|
import { fileURLToPath } from "url";
|
|
9
9
|
import { dirname } from "path";
|
|
10
10
|
|
|
@@ -12,6 +12,37 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
12
12
|
const CONFIG_PATH = join(homedir(), ".claude-friends.json");
|
|
13
13
|
|
|
14
14
|
const command = process.argv[2];
|
|
15
|
+
const args = process.argv.slice(3).join(" ").trim();
|
|
16
|
+
|
|
17
|
+
// Helper: connect, send a message, wait for response, print, exit
|
|
18
|
+
function run(messageType, payload, responseType, formatter) {
|
|
19
|
+
const config = getConfig();
|
|
20
|
+
if (!config) {
|
|
21
|
+
console.log("Not set up. Run: claude-friends setup");
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const ws = createConnection(config.username);
|
|
26
|
+
|
|
27
|
+
ws.addEventListener("open", () => {
|
|
28
|
+
ws.send(JSON.stringify({ type: messageType, ...payload }));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
ws.addEventListener("message", (event) => {
|
|
32
|
+
const msg = JSON.parse(event.data);
|
|
33
|
+
if (msg.type === responseType || msg.type === "error") {
|
|
34
|
+
if (msg.type === "error") {
|
|
35
|
+
console.log("Error:", msg.message);
|
|
36
|
+
} else {
|
|
37
|
+
console.log(formatter(msg));
|
|
38
|
+
}
|
|
39
|
+
ws.close();
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
setTimeout(() => { console.log("Timeout connecting to server."); process.exit(1); }, 5000);
|
|
45
|
+
}
|
|
15
46
|
|
|
16
47
|
if (command === "setup") {
|
|
17
48
|
const existing = getConfig();
|
|
@@ -64,7 +95,6 @@ if (command === "setup") {
|
|
|
64
95
|
if (!settings.hooks) settings.hooks = {};
|
|
65
96
|
if (!settings.hooks.Stop) settings.hooks.Stop = [];
|
|
66
97
|
|
|
67
|
-
// Check if hook already installed
|
|
68
98
|
const alreadyInstalled = settings.hooks.Stop.some((h) =>
|
|
69
99
|
h.hooks?.some((hk) => hk.command?.includes("update-tokens"))
|
|
70
100
|
);
|
|
@@ -84,45 +114,112 @@ if (command === "setup") {
|
|
|
84
114
|
console.log("Could not install token hook (non-critical):", err.message);
|
|
85
115
|
}
|
|
86
116
|
|
|
117
|
+
// Install statusline
|
|
118
|
+
try {
|
|
119
|
+
const settingsPath2 = join(homedir(), ".claude", "settings.json");
|
|
120
|
+
let settings = {};
|
|
121
|
+
if (existsSync(settingsPath2)) {
|
|
122
|
+
settings = JSON.parse(readFileSync(settingsPath2, "utf-8"));
|
|
123
|
+
}
|
|
124
|
+
if (!settings.statusLine) {
|
|
125
|
+
settings.statusLine = {
|
|
126
|
+
type: "command",
|
|
127
|
+
command: `node ${join(__dirname, "statusline.js")}`,
|
|
128
|
+
};
|
|
129
|
+
writeFileSync(settingsPath2, JSON.stringify(settings, null, 2));
|
|
130
|
+
console.log("Installed status line.");
|
|
131
|
+
}
|
|
132
|
+
} catch {}
|
|
133
|
+
|
|
87
134
|
console.log(`
|
|
88
135
|
Done! You're "${username.trim()}".
|
|
89
136
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
claude mcp add claude-friends -- claude-friends serve
|
|
93
|
-
|
|
94
|
-
Then in Claude Code:
|
|
137
|
+
In Claude Code:
|
|
95
138
|
/friend alice Add a friend
|
|
96
139
|
/friends See who's online
|
|
97
|
-
/nudge bob
|
|
140
|
+
/nudge bob hey! Nudge someone
|
|
98
141
|
/status debugging Set your status
|
|
99
142
|
/unfriend alice Remove a friend
|
|
100
143
|
|
|
101
|
-
Token usage is shared automatically
|
|
144
|
+
Token usage is shared automatically.
|
|
102
145
|
`);
|
|
103
146
|
|
|
104
147
|
rl.close();
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
148
|
+
|
|
149
|
+
} else if (command === "add") {
|
|
150
|
+
if (!args) { console.log("Usage: claude-friends add <username>"); process.exit(1); }
|
|
151
|
+
run("add-friend", { friend: args }, "friend-added", () => `Added ${args} as a friend!`);
|
|
152
|
+
|
|
153
|
+
} else if (command === "remove") {
|
|
154
|
+
if (!args) { console.log("Usage: claude-friends remove <username>"); process.exit(1); }
|
|
155
|
+
run("remove-friend", { friend: args }, "friend-removed", () => `Removed ${args}.`);
|
|
156
|
+
|
|
157
|
+
} else if (command === "online" || command === "list") {
|
|
158
|
+
run("get-friends", {}, "friends-list", (msg) => {
|
|
159
|
+
const friends = msg.friends || [];
|
|
160
|
+
if (friends.length === 0) return "No friends yet. Run: claude-friends add <username>";
|
|
161
|
+
|
|
162
|
+
const sorted = [...friends].sort((a, b) => (b.online ? 1 : 0) - (a.online ? 1 : 0));
|
|
163
|
+
const onlineCount = sorted.filter((f) => f.online).length;
|
|
164
|
+
|
|
165
|
+
const lines = sorted.map((f) => {
|
|
166
|
+
const dot = f.online ? "🟢" : "⚫";
|
|
167
|
+
const status = f.status && f.status !== "offline" && f.status !== "unknown" ? ` — ${f.status}` : "";
|
|
168
|
+
const tokens = f.tokensUsed ? ` [${(f.tokensUsed / 1000).toFixed(1)}K tokens]` : "";
|
|
169
|
+
return `${dot} ${f.name}${status}${tokens}`;
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
return `Friends (${onlineCount}/${friends.length} online):\n${lines.join("\n")}`;
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
} else if (command === "status") {
|
|
176
|
+
if (!args) { console.log("Usage: claude-friends status <message>"); process.exit(1); }
|
|
177
|
+
const config = getConfig();
|
|
178
|
+
if (!config) { console.log("Not set up. Run: claude-friends setup"); process.exit(1); }
|
|
179
|
+
const ws = createConnection(config.username);
|
|
180
|
+
ws.addEventListener("open", () => {
|
|
181
|
+
ws.send(JSON.stringify({ type: "set-status", status: args }));
|
|
182
|
+
console.log(`Status set: "${args}"`);
|
|
183
|
+
setTimeout(() => { ws.close(); process.exit(0); }, 500);
|
|
184
|
+
});
|
|
185
|
+
setTimeout(() => process.exit(1), 5000);
|
|
186
|
+
|
|
187
|
+
} else if (command === "nudge") {
|
|
188
|
+
const parts = args.split(" ");
|
|
189
|
+
const friend = parts[0];
|
|
190
|
+
const message = parts.slice(1).join(" ") || undefined;
|
|
191
|
+
if (!friend) { console.log("Usage: claude-friends nudge <username> [message]"); process.exit(1); }
|
|
192
|
+
run("nudge", { friend, message }, "nudge-sent", () => `Nudge sent to ${friend}!`);
|
|
193
|
+
|
|
108
194
|
} else if (command === "whoami") {
|
|
109
195
|
const config = getConfig();
|
|
110
|
-
if (!config) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
196
|
+
if (!config) { console.log("Not set up. Run: claude-friends setup"); process.exit(1); }
|
|
197
|
+
console.log(config.username);
|
|
198
|
+
|
|
199
|
+
} else if (command === "serve") {
|
|
200
|
+
// Keep for backwards compat with anyone who set up MCP
|
|
201
|
+
await import("./mcp-server.js");
|
|
202
|
+
|
|
115
203
|
} else {
|
|
116
204
|
console.log(`
|
|
117
205
|
claude-friends — social presence for Claude Code
|
|
118
206
|
|
|
119
207
|
Commands:
|
|
120
|
-
setup
|
|
121
|
-
|
|
122
|
-
|
|
208
|
+
setup Pick a username (one-time)
|
|
209
|
+
add <username> Add a friend
|
|
210
|
+
remove <username> Remove a friend
|
|
211
|
+
online See who's online
|
|
212
|
+
status <message> Set your status
|
|
213
|
+
nudge <user> [msg] Nudge a friend
|
|
214
|
+
whoami Show your username
|
|
123
215
|
|
|
124
216
|
Quick start:
|
|
125
217
|
claude-friends setup
|
|
126
|
-
claude
|
|
218
|
+
claude-friends add alice
|
|
219
|
+
|
|
220
|
+
In Claude Code:
|
|
221
|
+
/friend alice
|
|
222
|
+
/friends
|
|
223
|
+
/nudge bob hey!
|
|
127
224
|
`);
|
|
128
225
|
}
|
package/commands/friend.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
Run `claude-friends add $ARGUMENTS` and show the output. If no username is provided, ask for one first.
|
package/commands/friends.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
Run `claude-friends online` and show the output.
|
package/commands/nudge.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
Run `claude-friends nudge $ARGUMENTS` and show the output. If no username is provided, ask for one first.
|
package/commands/status.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
Run `claude-friends status $ARGUMENTS` and show the output. If no status message is provided, ask for one first.
|
package/commands/unfriend.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
Run `claude-friends remove $ARGUMENTS` and show the output. If no username is provided, ask for one first.
|