claude-friends 0.2.1 → 0.3.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/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
- ## What you get
18
+ ## Usage
20
19
 
21
- A status line in Claude Code showing online friends, plus these tools:
20
+ ### Slash commands (inside Claude Code)
22
21
 
23
- | Command | What it does |
24
- |---|---|
25
- | "who's online?" | See friends with 🟢/⚫ indicators |
26
- | "add friend alice" | Add someone by username |
27
- | "set my status to debugging auth" | Share what you're working on |
28
- | "nudge bob" | Poke a friend with a message |
29
- | "share my token usage: 45000" | Let friends see your token count |
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
- Add to your Claude Code settings (`~/.claude/settings.json`):
30
+ ### CLI (from any terminal)
35
31
 
36
- ```json
37
- {
38
- "statusLine": {
39
- "type": "command",
40
- "command": "node /path/to/claude-friends/statusline.js"
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
- Or after global install:
40
+ ## Features
46
41
 
47
- ```json
48
- {
49
- "statusLine": {
50
- "type": "command",
51
- "command": "claude-friends statusline"
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 open Claude Code → you go online
60
- - When you close it PartyKit detects the disconnect → you go offline
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
- Now add the MCP server to Claude Code:
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 Nudge someone
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 with friends.
144
+ Token usage is shared automatically.
102
145
  `);
103
146
 
104
147
  rl.close();
105
- } else if (command === "serve") {
106
- // Start the MCP server directly
107
- await import("./mcp-server.js");
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
- console.log("Not set up yet. Run: claude-friends setup");
112
- } else {
113
- console.log(config.username);
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 Pick a username (one-time)
121
- serve Start the MCP server (used by Claude Code)
122
- whoami Show your username
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 mcp add claude-friends -- claude-friends serve
218
+ claude-friends add alice
219
+
220
+ In Claude Code:
221
+ /friend alice
222
+ /friends
223
+ /nudge bob hey!
127
224
  `);
128
225
  }
@@ -1 +1 @@
1
- Use the add-friend MCP tool to add "$ARGUMENTS" as a friend. If no username is provided, ask for one.
1
+ Run `claude-friends add $ARGUMENTS` and show the output. If no username is provided, ask for one first.
@@ -1 +1 @@
1
- Use the friends-online MCP tool to show who's currently online.
1
+ Run `claude-friends online` and show the output.
package/commands/nudge.md CHANGED
@@ -1 +1 @@
1
- Use the nudge MCP tool to nudge "$ARGUMENTS". If the input includes a message after the username, pass it as the message parameter. If no username is provided, ask for one.
1
+ Run `claude-friends nudge $ARGUMENTS` and show the output. If no username is provided, ask for one first.
@@ -1 +1 @@
1
- Use the set-status MCP tool to set your status to "$ARGUMENTS". If no status is provided, ask what to set it to.
1
+ Run `claude-friends status $ARGUMENTS` and show the output. If no status message is provided, ask for one first.
@@ -1 +1 @@
1
- Use the remove-friend MCP tool to remove "$ARGUMENTS" from your friends list. If no username is provided, ask for one.
1
+ Run `claude-friends remove $ARGUMENTS` and show the output. If no username is provided, ask for one first.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-friends",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "See who's online in Claude Code. Add friends, share status, nudge each other.",
5
5
  "type": "module",
6
6
  "bin": {