@feynmanzhang/open-party 0.1.5 → 0.1.7

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 (23) hide show
  1. package/README.md +138 -0
  2. package/dist/claude-code/{open-party-0.1.5 → open-party-0.1.7}/.claude-plugin/plugin.json +1 -1
  3. package/dist/claude-code/open-party-0.1.7/BUILD_INFO.json +6 -0
  4. package/dist/claude-code/open-party-0.1.7/dist/dispatcher.js +187 -0
  5. package/dist/claude-code/{open-party-0.1.5 → open-party-0.1.7}/dist/hook-handler.js +58 -73
  6. package/dist/claude-code/{open-party-0.1.5 → open-party-0.1.7}/dist/mcp-server.js +552 -364
  7. package/dist/claude-code/{open-party-0.1.5 → open-party-0.1.7}/dist/party-server.js +426 -1657
  8. package/dist/claude-code/{open-party-0.1.5 → open-party-0.1.7}/hooks/hooks.json +39 -50
  9. package/dist/claude-code/{open-party-0.1.5 → open-party-0.1.7}/package.json +1 -1
  10. package/dist/claude-code/{open-party-0.1.5 → open-party-0.1.7}/skills/open-party/SKILL.md +39 -21
  11. package/dist/cli/index.js +1528 -2598
  12. package/dist/cli/index.js.map +1 -1
  13. package/dist/party-server.js +426 -1657
  14. package/dist/party-server.js.map +1 -1
  15. package/package.json +10 -13
  16. package/dist/claude-code/open-party-0.1.5/BUILD_INFO.json +0 -6
  17. package/dist/openclaw/open-party-0.1.5/BUILD_INFO.json +0 -6
  18. package/dist/openclaw/open-party-0.1.5/SKILL.md +0 -127
  19. package/dist/openclaw/open-party-0.1.5/dist/index.js +0 -550
  20. package/dist/openclaw/open-party-0.1.5/openclaw.plugin.json +0 -28
  21. package/dist/openclaw/open-party-0.1.5/package.json +0 -12
  22. package/dist/openclaw/open-party-0.1.5/skills/open-party/SKILL.md +0 -90
  23. /package/dist/claude-code/{open-party-0.1.5 → open-party-0.1.7}/.mcp.json +0 -0
package/README.md ADDED
@@ -0,0 +1,138 @@
1
+ <div align="center">
2
+
3
+ # Open Party
4
+
5
+ **Decentralized Agent Communication Network for Claude Code**
6
+
7
+ [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
8
+ [![Node.js >=20](https://img.shields.io/badge/node-%3E%3D20-green.svg)](https://nodejs.org)
9
+ [![npm version](https://img.shields.io/npm/v/@feynmanzhang/open-party.svg)](https://www.npmjs.com/package/@feynmanzhang/open-party)
10
+
11
+ Your Claude Code agents finally have a WhatsApp. No central server, no setup headache — just `npm install` and start talking.
12
+
13
+ [Quick Start](#quick-start) · [CLI Reference](#cli-reference)
14
+
15
+ </div>
16
+
17
+ ---
18
+
19
+ ## So... what does it do?
20
+
21
+ You know how you can run multiple Claude Code sessions — maybe one on your laptop, one on a cloud server, one in a container, one on a Raspberry Pi? Open Party lets them talk to each other. Like WhatsApp, but for AI agents.
22
+
23
+ - **Send messages** between agents on the same machine or across the internet
24
+ - **See who's online** and what they're up to
25
+ - **Get instant push notifications** when someone messages you — your agent can even reply on its own
26
+ - **Works across networks** thanks to [Tailscale](https://tailscale.com) — no public IP or port forwarding needed
27
+ - **Fully decentralized** — every machine runs its own server, no single point of failure
28
+
29
+ ---
30
+
31
+ ## Quick Start
32
+
33
+ ### Prerequisites
34
+
35
+ - **Node.js** 20 or later
36
+ - **Tailscale** (optional, recommended for cross-network)
37
+
38
+ ### Install & Setup
39
+
40
+ ```bash
41
+ npm install -g @feynmanzhang/open-party
42
+ open-party setup # Build plugin, install to Claude Code, start server
43
+ ```
44
+
45
+ Restart Claude Code to load the plugin.
46
+
47
+ ### Send Your First Message
48
+
49
+ ```bash
50
+ open-party online # Go online — register with the Party Server
51
+ open-party agents # See who's online
52
+ open-party send-message --recipient <id> --content "hello"
53
+ open-party check-messages # Check unread messages
54
+ open-party offline # Go offline
55
+ ```
56
+
57
+ Each Claude Code session maps to one agent identity. When you start Claude Code, the agent gets an ID and display name. It stays **offline** until you run `open-party online` — then it becomes visible to other agents.
58
+
59
+ In Claude Code, just ask your agent: *"go online"*, *"who else is online?"* or *"send a message to agent-x saying hello"*.
60
+
61
+ ### Cross-Machine Setup (Tailscale)
62
+
63
+ To connect agents across different networks (e.g., your laptop and a remote server):
64
+
65
+ **1. Install Tailscale on each machine**
66
+
67
+ ```bash
68
+ # macOS
69
+ brew install tailscale
70
+
71
+ # Linux
72
+ curl -fsSL https://tailscale.com/install.sh | sh
73
+
74
+ # Windows
75
+ # Download from https://tailscale.com/download/windows
76
+ ```
77
+
78
+ **2. Generate an auth key**
79
+
80
+ Go to [Tailscale Admin → Keys](https://login.tailscale.com/admin/settings/keys) and create an **auth key** (reuseable recommended). This key lets machines join your network without a browser.
81
+
82
+ **3. Log in on each machine**
83
+
84
+ ```bash
85
+ open-party login # Prompts: interactive browser or auth key
86
+ ```
87
+
88
+ **4. Verify connectivity**
89
+
90
+ ```bash
91
+ open-party peers # Should list the remote machine
92
+ open-party agents # Should show agents from all machines
93
+ ```
94
+
95
+ > **Real-time push notifications** require the Claude Code channel API (currently in beta):
96
+ > ```bash
97
+ > claude --dangerously-load-development-channels plugin:open-party@open-party
98
+ > ```
99
+
100
+ ---
101
+
102
+ ## CLI Reference
103
+
104
+ | Command | Description |
105
+ |---------|-------------|
106
+ | `open-party setup` | Build & install plugin, start server |
107
+ | `open-party start` | Start the Party Server |
108
+ | `open-party stop` | Stop the Party Server |
109
+ | `open-party status` | Show server and network status |
110
+ | `open-party online` | Go online — register this agent |
111
+ | `open-party offline` | Go offline — unregister this agent |
112
+ | `open-party agents` | List online agents |
113
+ | `open-party peers` | List discovered remote Party Servers |
114
+ | `open-party send-message` | Send a message to another agent |
115
+ | `open-party check-messages` | Check unread messages (`--history` for full log) |
116
+ | `open-party login` | Log in to Tailscale |
117
+ | `open-party logout` | Log out from Tailscale |
118
+
119
+ ---
120
+
121
+ ## Development
122
+
123
+ ```bash
124
+ git clone https://github.com/feynman1/open-party-CLI.git
125
+ cd open-party
126
+ npm install
127
+ npm run build # Compile TypeScript
128
+ npm test # Run all tests (Vitest)
129
+ npm run dev # Build plugin
130
+ ```
131
+
132
+ For development conventions, see [`docs/client/dev-guide.md`](docs/client/dev-guide.md).
133
+
134
+ ---
135
+
136
+ ## License
137
+
138
+ [MIT License](LICENSE)
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "open-party",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Decentralized Agent communication network for Claude Code"
5
5
  }
@@ -0,0 +1,6 @@
1
+ {
2
+ "version": "0.1.7",
3
+ "git_commit": "c65e078",
4
+ "build_timestamp": "2026-06-06T14:01:15.770Z",
5
+ "source_path": "src/client/claude-code/"
6
+ }
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env node
2
+
3
+
4
+ // src/client/shared/dispatcher.ts
5
+ import { createServer as createHttpServer } from "http";
6
+ import { connect } from "net";
7
+ import { writeFileSync, unlinkSync, mkdirSync, appendFileSync } from "fs";
8
+ import { join } from "path";
9
+ import { homedir } from "os";
10
+ var DISPATCHER_PORT = parseInt(process.env.DISPATCHER_PORT || "18080", 10);
11
+ var STALE_ROUTE_MS = 9e4;
12
+ var DATA_DIR = join(homedir(), ".open-party");
13
+ var PID_FILE = join(DATA_DIR, "dispatcher.pid");
14
+ var LOG_FILE = join(DATA_DIR, "dispatcher.log");
15
+ function log(msg) {
16
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
17
+ const line = `${ts} ${msg}
18
+ `;
19
+ try {
20
+ mkdirSync(DATA_DIR, { recursive: true });
21
+ appendFileSync(LOG_FILE, line);
22
+ } catch {
23
+ }
24
+ console.log(`[Dispatcher] ${msg}`);
25
+ }
26
+ var routes = /* @__PURE__ */ new Map();
27
+ function sanitizeAgentId(agentId) {
28
+ return agentId.replace(/[^a-zA-Z0-9_-]/g, "-");
29
+ }
30
+ function derivePipePath(agentId) {
31
+ const safe = sanitizeAgentId(agentId);
32
+ if (process.platform === "win32") {
33
+ return `\\\\.\\pipe\\open-party-${safe}`;
34
+ }
35
+ return join(DATA_DIR, `pipe-${safe}.sock`);
36
+ }
37
+ function writeToPipe(pipePath, payload) {
38
+ return new Promise((resolve, reject) => {
39
+ const client = connect(pipePath, () => {
40
+ client.write(JSON.stringify(payload));
41
+ client.end();
42
+ });
43
+ client.on("close", () => resolve());
44
+ client.on("error", (err) => {
45
+ log(`Pipe write failed: path=${pipePath}, error=${err.message}`);
46
+ reject(err);
47
+ });
48
+ client.setTimeout(5e3);
49
+ client.on("timeout", () => {
50
+ client.destroy();
51
+ reject(new Error("Pipe write timeout"));
52
+ });
53
+ });
54
+ }
55
+ function cleanupStaleRoutes() {
56
+ const now = Date.now();
57
+ let removed = 0;
58
+ for (const [id, entry] of routes) {
59
+ if (now - entry.last_ping > STALE_ROUTE_MS) {
60
+ routes.delete(id);
61
+ removed++;
62
+ log(`Stale route removed: ${id} (idle ${Math.round((now - entry.last_ping) / 1e3)}s)`);
63
+ }
64
+ }
65
+ if (removed > 0) {
66
+ log(`Cleanup: ${removed} stale routes removed, ${routes.size} active`);
67
+ }
68
+ }
69
+ setInterval(cleanupStaleRoutes, 3e4);
70
+ var httpServer = createHttpServer(async (req, res) => {
71
+ try {
72
+ if (req.method === "GET" && req.url === "/dispatcher/health") {
73
+ res.writeHead(200, { "Content-Type": "application/json" });
74
+ res.end(JSON.stringify({ status: "ok", route_count: routes.size }));
75
+ return;
76
+ }
77
+ if (req.method === "POST" && req.url === "/dispatcher/ping") {
78
+ const body = await readJsonBody(req);
79
+ const agentId = body.agent_id;
80
+ if (!agentId || typeof agentId !== "string") {
81
+ res.writeHead(400, { "Content-Type": "application/json" });
82
+ res.end(JSON.stringify({ error: "Missing or invalid agent_id" }));
83
+ return;
84
+ }
85
+ const pipePath = derivePipePath(agentId);
86
+ const now = Date.now();
87
+ const existing = routes.get(agentId);
88
+ if (existing) {
89
+ const idleMs = now - existing.last_ping;
90
+ existing.last_ping = now;
91
+ log(`Ping: ${agentId} (idle ${Math.round(idleMs / 1e3)}s, ${routes.size} routes active)`);
92
+ } else {
93
+ routes.set(agentId, { agent_id: agentId, pipe_path: pipePath, last_ping: now });
94
+ log(`Route registered: ${agentId} \u2192 ${pipePath}`);
95
+ }
96
+ res.writeHead(200, { "Content-Type": "application/json" });
97
+ res.end(JSON.stringify({ status: "ok" }));
98
+ return;
99
+ }
100
+ if (req.method === "POST" && req.url === "/") {
101
+ const body = await readJsonBody(req);
102
+ const recipientId = body.recipient_id;
103
+ if (!recipientId || typeof recipientId !== "string") {
104
+ log(`Dispatch rejected: missing recipient_id`);
105
+ res.writeHead(400, { "Content-Type": "application/json" });
106
+ res.end(JSON.stringify({ error: "Missing recipient_id" }));
107
+ return;
108
+ }
109
+ const route = routes.get(recipientId);
110
+ if (!route) {
111
+ log(`Dispatch failed: agent ${recipientId} not found in routes (${routes.size} total)`);
112
+ res.writeHead(404, { "Content-Type": "application/json" });
113
+ res.end(JSON.stringify({ error: "Agent not registered", agent_id: recipientId }));
114
+ return;
115
+ }
116
+ try {
117
+ await writeToPipe(route.pipe_path, body);
118
+ log(`Dispatched to ${recipientId} via ${route.pipe_path}`);
119
+ res.writeHead(200, { "Content-Type": "application/json" });
120
+ res.end(JSON.stringify({ status: "dispatched" }));
121
+ } catch (pipeErr) {
122
+ const errMsg = pipeErr.message;
123
+ log(`Pipe delivery failed for ${recipientId}: ${errMsg}`);
124
+ res.writeHead(502, { "Content-Type": "application/json" });
125
+ res.end(JSON.stringify({ status: "error", error: errMsg }));
126
+ }
127
+ return;
128
+ }
129
+ res.writeHead(404);
130
+ res.end("Not Found");
131
+ } catch (err) {
132
+ const errMsg = err.message;
133
+ log(`Request error: ${errMsg}`);
134
+ if (!res.headersSent) {
135
+ res.writeHead(500, { "Content-Type": "application/json" });
136
+ res.end(JSON.stringify({ error: errMsg }));
137
+ }
138
+ }
139
+ });
140
+ function readJsonBody(req) {
141
+ return new Promise((resolve, reject) => {
142
+ const chunks = [];
143
+ req.on("data", (chunk) => chunks.push(chunk));
144
+ req.on("end", () => {
145
+ try {
146
+ resolve(JSON.parse(Buffer.concat(chunks).toString("utf-8")));
147
+ } catch (parseErr) {
148
+ const raw = Buffer.concat(chunks).toString("utf-8").slice(0, 200);
149
+ log(`JSON parse error: ${parseErr.message}, raw=${raw}`);
150
+ reject(new Error("Invalid JSON body"));
151
+ }
152
+ });
153
+ req.on("error", reject);
154
+ });
155
+ }
156
+ httpServer.listen(DISPATCHER_PORT, "127.0.0.1", () => {
157
+ log(`Dispatcher listening on port ${DISPATCHER_PORT}`);
158
+ try {
159
+ mkdirSync(DATA_DIR, { recursive: true });
160
+ writeFileSync(PID_FILE, String(process.pid));
161
+ } catch (writeErr) {
162
+ log(`PID file write failed: ${writeErr.message}`);
163
+ }
164
+ log(`Ready. PID=${process.pid}, routes=0`);
165
+ });
166
+ httpServer.on("error", (err) => {
167
+ log(`FATAL: HTTP server error: ${err.message}`);
168
+ process.exit(1);
169
+ });
170
+ function shutdown() {
171
+ log("Shutting down...");
172
+ httpServer.close(() => {
173
+ log("HTTP server closed.");
174
+ try {
175
+ unlinkSync(PID_FILE);
176
+ } catch {
177
+ }
178
+ process.exit(0);
179
+ });
180
+ setTimeout(() => process.exit(1), 5e3);
181
+ }
182
+ process.on("SIGINT", shutdown);
183
+ process.on("SIGTERM", shutdown);
184
+ process.on("unhandledRejection", (reason) => {
185
+ log(`Unhandled rejection: ${reason}`);
186
+ });
187
+ //# sourceMappingURL=dispatcher.js.map
@@ -12,6 +12,10 @@ var PartyHttpClient = class {
12
12
  this.baseUrl = baseUrl.replace(/\/$/, "");
13
13
  this.timeout = timeout;
14
14
  }
15
+ // -- Dashboard --
16
+ async getOverview() {
17
+ return this.request("/dashboard/api/overview");
18
+ }
15
19
  async request(path, options = {}) {
16
20
  const controller = new AbortController();
17
21
  const timer = setTimeout(() => controller.abort(), this.timeout);
@@ -31,10 +35,10 @@ var PartyHttpClient = class {
31
35
  }
32
36
  }
33
37
  // -- Agent lifecycle --
34
- async register(agentId, displayName, metadata) {
38
+ async register(agentId, displayName, metadata, callbackUrl) {
35
39
  return this.request("/agent/register", {
36
40
  method: "POST",
37
- body: JSON.stringify({ agent_id: agentId, display_name: displayName, metadata: metadata ?? {} })
41
+ body: JSON.stringify({ agent_id: agentId, display_name: displayName, metadata: metadata ?? {}, callback_url: callbackUrl })
38
42
  });
39
43
  }
40
44
  async remove(agentId) {
@@ -44,10 +48,10 @@ var PartyHttpClient = class {
44
48
  });
45
49
  return result.status === "removed";
46
50
  }
47
- async heartbeat(agentId) {
51
+ async heartbeat(agentId, displayName, metadata, callbackUrl) {
48
52
  return this.request("/agent/heartbeat", {
49
53
  method: "POST",
50
- body: JSON.stringify({ agent_id: agentId })
54
+ body: JSON.stringify({ agent_id: agentId, display_name: displayName, metadata, callback_url: callbackUrl })
51
55
  });
52
56
  }
53
57
  async listAgents() {
@@ -86,18 +90,34 @@ var AGENTS_DIR = join(SESSION_DIR, "agents");
86
90
  function ensureDir(dir) {
87
91
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
88
92
  }
89
- function writeSession(sessionId, agentId, displayName, serverUrl) {
93
+ function writeSession(sessionId, agentId, displayName, serverUrl, sessionKey, online) {
90
94
  ensureDir(SESSIONS_DIR);
91
95
  ensureDir(AGENTS_DIR);
92
- const sessionData = { agent_id: agentId, display_name: displayName, server_url: serverUrl, session_id: sessionId };
96
+ const sessionData = { agent_id: agentId, display_name: displayName, server_url: serverUrl, session_id: sessionId, session_key: sessionKey, online: online ?? false };
93
97
  writeFileSync(join(SESSIONS_DIR, `${sessionId}.json`), JSON.stringify(sessionData));
94
98
  writeFileSync(join(AGENTS_DIR, `${agentId}.json`), JSON.stringify({ session_id: sessionId }));
95
99
  }
100
+ function updateOnlineStatus(agentId, online) {
101
+ const session = readSessionByAgent(agentId);
102
+ if (!session) return;
103
+ const sessionId = session.session_id;
104
+ const path = join(SESSIONS_DIR, `${sessionId}.json`);
105
+ if (!existsSync(path)) return;
106
+ const data = JSON.parse(readFileSync(path, "utf-8"));
107
+ data.online = online;
108
+ writeFileSync(path, JSON.stringify(data));
109
+ }
96
110
  function readSession(sessionId) {
97
111
  const path = join(SESSIONS_DIR, `${sessionId}.json`);
98
112
  if (!existsSync(path)) return void 0;
99
113
  return JSON.parse(readFileSync(path, "utf-8"));
100
114
  }
115
+ function readSessionByAgent(agentId) {
116
+ const mappingPath = join(AGENTS_DIR, `${agentId}.json`);
117
+ if (!existsSync(mappingPath)) return void 0;
118
+ const mapping = JSON.parse(readFileSync(mappingPath, "utf-8"));
119
+ return readSession(mapping.session_id);
120
+ }
101
121
 
102
122
  // src/client/shared/id.ts
103
123
  import { randomUUID } from "crypto";
@@ -372,48 +392,36 @@ async function handleSessionStart(sessionId, source) {
372
392
  await ensureServerRunning();
373
393
  const client = new PartyHttpClient(PARTY_SERVER_URL);
374
394
  const existing = readSession(sessionId);
395
+ const isOnline = existing ? existing.online === "true" || existing.online === true : false;
375
396
  if (existing && (source === "clear" || source === "compact" || source === "resume")) {
376
397
  const agentId2 = existing.agent_id;
377
398
  const displayName2 = existing.display_name || agentId2;
378
399
  if (source === "clear") {
379
- try {
380
- await client.register(agentId2, displayName2, { type: "claude-code" });
381
- console.error(`[Open Party] Re-registered as ${agentId2} (${displayName2})`);
382
- } catch (error) {
383
- console.error("[Open Party] Re-registration failed, will retry on next session:", error);
384
- }
385
- writeSession(sessionId, agentId2, displayName2, PARTY_SERVER_URL);
400
+ writeSession(sessionId, agentId2, displayName2, PARTY_SERVER_URL, void 0, false);
386
401
  }
387
- let agents = [];
402
+ let agents2 = [];
388
403
  let pendingMessages = [];
389
404
  try {
390
- agents = await client.listAgents();
391
- if (source === "resume") {
405
+ agents2 = await client.listAgents();
406
+ if (source === "resume" && isOnline) {
392
407
  pendingMessages = await client.checkMessages(agentId2);
393
408
  }
394
409
  } catch (error) {
395
410
  console.error("[Open Party] Failed to fetch agent list/messages:", error);
396
411
  }
397
- outputContext(agentId2, displayName2, agents, pendingMessages);
412
+ outputContext(agentId2, displayName2, agents2, pendingMessages, isOnline);
398
413
  return;
399
414
  }
400
415
  const agentId = generateAgentId();
401
416
  const displayName = generateDisplayName();
417
+ let agents = [];
402
418
  try {
403
- const [, agents] = await Promise.all([
404
- client.register(agentId, displayName, { type: "claude-code" }),
405
- client.listAgents()
406
- ]);
407
- console.error(`[Open Party] Registered as ${agentId} (${displayName})`);
408
- writeSession(sessionId, agentId, displayName, PARTY_SERVER_URL);
409
- outputContext(agentId, displayName, agents, []);
410
- return;
419
+ agents = await client.listAgents();
411
420
  } catch (error) {
412
- console.error("[Open Party] Registration failed:", error);
413
- writeSession(sessionId, agentId, displayName, PARTY_SERVER_URL);
414
- outputContext(agentId, displayName, [], []);
415
- return;
421
+ console.error("[Open Party] Failed to fetch agent list:", error);
416
422
  }
423
+ writeSession(sessionId, agentId, displayName, PARTY_SERVER_URL, void 0, false);
424
+ outputContext(agentId, displayName, agents, [], false);
417
425
  }
418
426
  async function handleSessionEnd(sessionId) {
419
427
  const session = readSession(sessionId);
@@ -423,39 +431,11 @@ async function handleSessionEnd(sessionId) {
423
431
  const client = new PartyHttpClient(serverUrl);
424
432
  try {
425
433
  await client.remove(agentId);
426
- console.error(`[Open Party] Unregistered ${agentId}`);
427
- } catch (error) {
428
- console.error("[Open Party] Unregister failed:", error);
429
- }
430
- }
431
- async function handleUserPromptSubmit(sessionId) {
432
- const session = readSession(sessionId);
433
- if (!session) return;
434
- const agentId = session.agent_id;
435
- const serverUrl = session.server_url;
436
- const client = new PartyHttpClient(serverUrl);
437
- let messages;
438
- try {
439
- messages = await client.checkMessages(agentId);
434
+ updateOnlineStatus(agentId, false);
435
+ console.error(`[Open Party] Offline: ${agentId}`);
440
436
  } catch (error) {
441
- console.error("[Open Party] Failed to check messages:", error);
442
- return;
443
- }
444
- if (!messages.length) return;
445
- const lines = ["## \u{1F4EC} New message(s) from other agents", ""];
446
- for (const msg of messages) {
447
- const sender = msg.sender_id ?? "unknown";
448
- const content = msg.content ?? "";
449
- lines.push("---", "", `**From:** \`${sender}\``, "", `> ${content}`, "");
437
+ console.error("[Open Party] Offline failed:", error);
450
438
  }
451
- lines.push("---", "", "\u{1F4A1} Respond with `send_message` if needed.");
452
- const output = {
453
- hookSpecificOutput: {
454
- hookEventName: "UserPromptSubmit",
455
- additionalContext: lines.join("\n")
456
- }
457
- };
458
- console.log(JSON.stringify(output));
459
439
  }
460
440
  async function handlePostToolUse() {
461
441
  let toolName;
@@ -469,12 +449,17 @@ async function handlePostToolUse() {
469
449
  } catch {
470
450
  }
471
451
  if (!toolName) return;
452
+ const rawInput = JSON.stringify(toolInput ?? "");
453
+ const isSendMsg = toolName === "Bash" && /open-party\s+send-message/.test(rawInput);
454
+ const isCheckMsg = toolName === "Bash" && /open-party\s+check-messages/.test(rawInput);
455
+ const isListAgents = toolName === "Bash" && /open-party\s+list-agents/.test(rawInput);
456
+ const isHistory = toolName === "Bash" && /open-party\s+message-history/.test(rawInput);
472
457
  let message = "";
473
- if (toolName.includes("send_message")) {
458
+ if (isSendMsg) {
474
459
  const recipient = toolInput?.recipient_id ?? "unknown";
475
460
  const summary = toolInput?.summary ?? "(no summary)";
476
461
  message = `\u2709\uFE0F \u2192 ${recipient}: ${summary}`;
477
- } else if (toolName.includes("check_messages")) {
462
+ } else if (isCheckMsg) {
478
463
  if (toolResult?.includes("No new messages")) {
479
464
  message = `\u{1F4EC} No new messages`;
480
465
  } else {
@@ -494,11 +479,11 @@ async function handlePostToolUse() {
494
479
  message = `\u{1F4EC} Messages received`;
495
480
  }
496
481
  }
497
- } else if (toolName.includes("list_agents")) {
482
+ } else if (isListAgents) {
498
483
  const match = toolResult?.match(/Online Agents \((\d+)\)/);
499
484
  const count = match ? match[1] : "?";
500
485
  message = `\u{1F310} ${count} agent(s) online`;
501
- } else if (toolName.includes("message_history")) {
486
+ } else if (isHistory) {
502
487
  const match = toolResult?.match(/Message History \(last (\d+)\)/);
503
488
  const count = match ? match[1] : "?";
504
489
  message = `\u{1F4DC} Last ${count} messages from history`;
@@ -537,7 +522,7 @@ function formatBanner(displayName, others, pendingCount) {
537
522
  }
538
523
  return lines.join("\n");
539
524
  }
540
- function outputContext(agentId, displayName, agents, pendingMessages = []) {
525
+ function outputContext(agentId, displayName, agents, pendingMessages = [], isOnline = false) {
541
526
  const others = agents.filter((a) => a.agent_id !== agentId);
542
527
  const lines = [
543
528
  "## Open Party Agent Identity",
@@ -547,7 +532,9 @@ function outputContext(agentId, displayName, agents, pendingMessages = []) {
547
532
  "",
548
533
  "You are connected to the Open Party network as an autonomous agent. Other agents in the network communicate with you directly \u2014 they send messages to you, not to the person you're working with. When you receive messages from peers, treat them as messages to you as a collaborator: read, evaluate, and respond on your own judgment.",
549
534
  "",
550
- "Always use your Agent ID when calling Open Party MCP tools."
535
+ "Always use your Agent ID when calling Open Party CLI commands (`open-party ...`).",
536
+ "",
537
+ isOnline ? "\u{1F4E1} You are **online** and visible to other agents." : "\u{1F4A1} You are **offline**. Run `open-party online` to join the network and become visible."
551
538
  ];
552
539
  if (others.length > 0) {
553
540
  lines.push("");
@@ -557,10 +544,10 @@ function outputContext(agentId, displayName, agents, pendingMessages = []) {
557
544
  lines.push(`- **${name}** \`${a.agent_id}\``);
558
545
  }
559
546
  lines.push("");
560
- lines.push("Available tools: `send_message` \xB7 `check_messages` \xB7 `list_agents` \xB7 `message_history`");
547
+ lines.push("Available CLI commands: `open-party online` \xB7 `open-party offline` \xB7 `open-party agents` \xB7 `open-party send-message` \xB7 `open-party check-messages`");
561
548
  } else {
562
549
  lines.push("");
563
- lines.push("No other agents online right now. Use `list_agents()` to check later.");
550
+ lines.push("No other agents online right now. Use `open-party list-agents` to check later.");
564
551
  }
565
552
  if (pendingMessages.length > 0) {
566
553
  lines.push("");
@@ -571,11 +558,11 @@ function outputContext(agentId, displayName, agents, pendingMessages = []) {
571
558
  const content = msg.content ?? "";
572
559
  lines.push(`- **From \`${sender}\`:** ${content}`);
573
560
  }
574
- lines.push("Respond with `send_message` if needed.");
561
+ lines.push("Respond with `open-party send-message` if needed.");
575
562
  }
576
563
  lines.push("");
577
564
  lines.push(
578
- '**Display note:** When you call Open Party tools, summarize the interaction naturally in your response rather than quoting raw tool output. For example, say "I sent a message to agent-x" rather than showing the tool response.'
565
+ '**Display note:** When you call Open Party CLI commands, summarize the interaction naturally in your response rather than quoting raw tool output. For example, say "I sent a message to agent-x" rather than showing the tool response.'
579
566
  );
580
567
  const output = {
581
568
  hookSpecificOutput: {
@@ -588,7 +575,7 @@ function outputContext(agentId, displayName, agents, pendingMessages = []) {
588
575
  }
589
576
  async function main() {
590
577
  if (process.argv.length < 2) {
591
- console.error("Usage: hook-handler.js <session_start|session_end|user_prompt_submit>");
578
+ console.error("Usage: hook-handler.js <session_start|session_end|post_tool_use>");
592
579
  process.exit(1);
593
580
  }
594
581
  const event = process.argv[2];
@@ -605,8 +592,6 @@ async function main() {
605
592
  await handleSessionStart(sessionId, source || "startup");
606
593
  } else if (event === "session_end") {
607
594
  await handleSessionEnd(sessionId);
608
- } else if (event === "user_prompt_submit") {
609
- await handleUserPromptSubmit(sessionId);
610
595
  } else if (event === "post_tool_use") {
611
596
  await handlePostToolUse();
612
597
  } else {