@vdoninja/ninja-p2p 0.1.4 → 0.2.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.
Files changed (46) hide show
  1. package/.agents/skills/ninja-p2p/SKILL.md +102 -2
  2. package/.claude/skills/ninja-p2p/SKILL.md +209 -130
  3. package/.codex/skills/ninja-p2p/SKILL.md +102 -2
  4. package/CHANGELOG.md +113 -0
  5. package/README.md +1077 -775
  6. package/dashboard.html +370 -50
  7. package/dist/agent-state.js +9 -0
  8. package/dist/cli-lib.d.ts +40 -0
  9. package/dist/cli-lib.js +165 -2
  10. package/dist/cli.d.ts +2 -0
  11. package/dist/cli.js +501 -10
  12. package/dist/demo.d.ts +37 -0
  13. package/dist/demo.js +186 -0
  14. package/dist/doctor.d.ts +52 -0
  15. package/dist/doctor.js +219 -0
  16. package/dist/file-transfer.d.ts +15 -2
  17. package/dist/file-transfer.js +249 -15
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.js +1 -1
  20. package/dist/message-bus.d.ts +15 -0
  21. package/dist/message-bus.js +32 -3
  22. package/dist/peer-registry.js +7 -0
  23. package/dist/protocol.d.ts +78 -1
  24. package/dist/protocol.js +42 -6
  25. package/dist/shared-folders.js +20 -1
  26. package/dist/social-stream.d.ts +100 -0
  27. package/dist/social-stream.js +254 -0
  28. package/dist/swarm-manager.d.ts +164 -0
  29. package/dist/swarm-manager.js +957 -0
  30. package/dist/swarm-session.d.ts +197 -0
  31. package/dist/swarm-session.js +465 -0
  32. package/dist/swarm-wire.d.ts +48 -0
  33. package/dist/swarm-wire.js +86 -0
  34. package/dist/swarm.d.ts +258 -0
  35. package/dist/swarm.js +694 -0
  36. package/dist/vdo-bridge.d.ts +62 -1
  37. package/dist/vdo-bridge.js +273 -23
  38. package/dist/vdoninja-sdk-types.d.ts +75 -0
  39. package/dist/vdoninja-sdk-types.js +10 -0
  40. package/dist/wake.d.ts +83 -0
  41. package/dist/wake.js +206 -0
  42. package/docs/protocol-and-reliability.md +231 -38
  43. package/docs/sdk-wishlist.md +236 -0
  44. package/docs/security.md +197 -0
  45. package/docs/social-stream-bridge.md +390 -0
  46. package/package.json +125 -116
package/README.md CHANGED
@@ -1,775 +1,1077 @@
1
- # ninja-p2p
2
-
3
- ## TL;DR
4
-
5
- Think of `ninja-p2p` as a small group chat for AI helpers. Put Codex, Claude, your own bots, and optionally a human operator in the same room. They can see who is there, send messages, ask each other for work, exchange files, and keep a small inbox while another agent is busy.
6
-
7
- It runs over [VDO.Ninja](https://vdo.ninja) WebRTC data channels, so you do not need to build or host a new chat server.
8
-
9
- **Best for:** always-on agent rooms, shell automation, and agents that need a local inbox.
10
-
11
- **Not for:** general network tunnelling, durable cloud storage, or very large public communities.
12
-
13
- Package: [`@vdoninja/ninja-p2p`](https://www.npmjs.com/package/@vdoninja/ninja-p2p) | [Protocol and reliability](docs/protocol-and-reliability.md) | [Support](https://discord.vdo.ninja)
14
-
15
- <p align="center">
16
- <a href="docs/images/agent-room-dashboard.png"><img src="docs/images/agent-room-dashboard.png" alt="A live Ninja P2P room with Planner and Reviewer agents exchanging messages while an operator watches" width="900"></a>
17
- </p>
18
-
19
- <p align="center"><em>A real room: two agent sidecars talking while a human watches from the browser dashboard.</em></p>
20
-
21
- ## The Simple Mental Model
22
-
23
- - A **room** is the shared meeting place.
24
- - A **sidecar** keeps one agent connected and holds its local inbox.
25
- - The **CLI or skill** lets Codex and Claude read and write that inbox during their turns.
26
- - The optional **dashboard** lets a person watch, chat, inspect agents, and download shared files.
27
-
28
- ## Start Two Agents
29
-
30
- Install once:
31
-
32
- ```bash
33
- npm install -g @vdoninja/ninja-p2p @roamhq/wrtc
34
- ```
35
-
36
- Start the first agent. A private room name is generated automatically:
37
-
38
- ```bash
39
- ninja-p2p start --id codex
40
- ninja-p2p room --id codex
41
- ```
42
-
43
- Use the room name printed above to start the second agent:
44
-
45
- ```bash
46
- ninja-p2p start --room <room-name> --id claude
47
- ```
48
-
49
- Now they can talk:
50
-
51
- ```bash
52
- ninja-p2p dm --id codex claude "Please review my rollout plan"
53
- ninja-p2p notify --id claude
54
- ninja-p2p read --id claude --take 10
55
- ninja-p2p dm --id claude codex "I found two risks; sending notes now"
56
- ```
57
-
58
- That is the core product. Profiles, commands, approvals, file transfer, shared folders, and the dashboard build on the same room and inbox.
59
-
60
- ## Make It Feel Native In Codex Or Claude
61
-
62
- The optional skill teaches the agent when and how to use the CLI:
63
-
64
- ```bash
65
- ninja-p2p install-skill codex
66
- ninja-p2p install-skill claude
67
- ```
68
-
69
- - In **Claude Code**, use `/ninja-p2p start`, `/ninja-p2p notify`, and `/ninja-p2p read`.
70
- - In **Codex**, mention `$ninja-p2p` or let Codex run the `ninja-p2p` command directly.
71
- - Restart the client after installing a skill if it does not appear immediately.
72
-
73
- ## Which VDO.Ninja Package Do I Need?
74
-
75
- | Your goal | Use |
76
- | --- | --- |
77
- | Give agents a persistent room and inbox | **`@vdoninja/ninja-p2p`** |
78
- | Give an MCP client connect/send/file/state tools | [`@vdoninja/mcp`](https://github.com/steveseguin/ninjamcp) |
79
- | Build directly with WebRTC media or data channels | [`@vdoninja/sdk`](https://github.com/steveseguin/ninjasdk) |
80
-
81
- `ninja-p2p` adds only its agent-friendly message envelope and local sidecar state. It uses VDO.Ninja's existing signaling behavior and does not invent new WebSocket commands.
82
-
83
- ## How Joining A Room Works
84
-
85
- - The first agent can start with no `--room`, and `ninja-p2p` will generate one.
86
- - Run `room` on that first agent to see the exact room name.
87
- - Every other agent must start with that same `--room`.
88
-
89
- Examples:
90
-
91
- ```text
92
- /ninja-p2p start
93
- /ninja-p2p room
94
- ```
95
-
96
- ```bash
97
- ninja-p2p start --room clawd_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx --id codex
98
- ```
99
-
100
- ## Claude And Codex Talking To Each Other
101
-
102
- If you want Claude and Codex in the same room, start one sidecar for each in the same room name:
103
-
104
- ```bash
105
- ninja-p2p start --room ai-room --id claude
106
- ninja-p2p start --room ai-room --id codex
107
- ```
108
-
109
- Then:
110
-
111
- - in Claude Code, use `/ninja-p2p dm codex "Can you review this?"`
112
- - in Codex, use `ninja-p2p notify --id codex` and `ninja-p2p read --id codex --take 10`
113
- - Codex can answer with `ninja-p2p dm --id codex claude "I pushed a fix"`
114
-
115
- ## Raw CLI
116
-
117
- If you just want the lower-level shell commands without Claude Code or Codex in the loop:
118
-
119
- ```bash
120
- ninja-p2p connect --room my-room --name Steve --id steve
121
- ninja-p2p chat --room my-room --name Steve --id steve "hello"
122
- ninja-p2p dm --room my-room --name Steve --id steve claude "ping"
123
- ninja-p2p send-file --room my-room --name Steve --id steve claude ./notes.txt
124
- ninja-p2p start --room my-room --name Claude --id claude --share docs=./docs
125
- ninja-p2p shares --id steve claude
126
- ninja-p2p list-files --id steve claude docs
127
- ninja-p2p get-file --id steve claude docs guide.md
128
- ```
129
-
130
- Inside `connect`, type a message and press Enter. Use `/help` for direct messages, commands, events, status updates, and peer listing.
131
-
132
- ## What It Is
133
-
134
- - a small npm package and shell CLI for agent-to-agent messaging
135
- - WebRTC data-channel transport on top of VDO.Ninja
136
- - shared rooms, private messages, command messages, topic events, and peer presence
137
- - simple file and image transfer between CLI agents
138
- - explicit named shared folders that peers can list and pull from
139
- - usable from Node bots, a browser dashboard, Codex CLI, or Claude Code
140
-
141
- ## What It Is Not
142
-
143
- - not a VPN
144
- - not a generic TCP tunnel
145
- - not a generic HTTP tunnel
146
- - not an MCP server
147
- - not durable storage
148
- - not a guaranteed-delivery transport
149
- - not a general network file share
150
-
151
- If you want to expose a private network or front a public website, use a VPN or tunnel built for that job. `ninja-p2p` is for peer coordination.
152
-
153
- If you want file sharing, keep the mental model narrow:
154
-
155
- - a sidecar exposes only the folders you explicitly declare with `--share`
156
- - peers can list those folders and request one file at a time
157
- - peers cannot browse arbitrary disk paths unless you shared them on purpose
158
-
159
- ## Optional Agent Profile Metadata
160
-
161
- You do not need this for day one. Start with `/ninja-p2p start` in Claude or `ninja-p2p start --id codex` in Codex first.
162
-
163
- If you want peers to know more about what an agent is good at, you can add optional metadata later:
164
-
165
- ```bash
166
- ninja-p2p start --room ai-room --id codex --runtime codex-cli --provider openai --model gpt-5 --can review,tests
167
- ```
168
-
169
- That extra metadata is only for discovery. It does not change the transport.
170
-
171
- ## Claude Code And Codex CLI
172
-
173
- The clean mental model is:
174
-
175
- - `ninja-p2p` is the npm package and shell command
176
- - skills are optional helpers that teach Claude Code or Codex how to use that command
177
- - MCP is a different integration path entirely
178
-
179
- If you want this to feel MCP-like inside Claude Code or Codex CLI, use the sidecar pattern below.
180
-
181
- ### Sidecar Pattern
182
-
183
- Start one persistent `ninja-p2p` process per agent. That process stays connected to the room and keeps a local inbox and outbox on disk.
184
-
185
- Codex sidecar:
186
-
187
- ```bash
188
- ninja-p2p start --room ai-room --name Codex --id codex --runtime codex-cli --provider openai --model gpt-5 --can review,tests --ask implement:"Implement a scoped change" --share docs=./docs
189
- ```
190
-
191
- Claude sidecar:
192
-
193
- ```bash
194
- ninja-p2p start --room ai-room --name Claude --id claude --runtime claude-code --provider anthropic --model sonnet --can plan,review --ask review:"Review a patch"
195
- ```
196
-
197
- That creates a local state folder at:
198
-
199
- - macOS/Linux: `~/.ninja-p2p/<id>`
200
- - Windows: `%USERPROFILE%\.ninja-p2p\<id>`
201
-
202
- Then the model can use cheap local commands on each turn:
203
-
204
- ```bash
205
- ninja-p2p status --id codex
206
- ninja-p2p notify --id codex
207
- ninja-p2p read --id codex --take 10
208
- ninja-p2p shares --id codex worker
209
- ninja-p2p list-files --id codex worker docs
210
- ninja-p2p get-file --id codex worker docs guide.md
211
- ninja-p2p send-file --id codex reviewer ./notes.txt
212
- ninja-p2p send-image --id codex reviewer ./diagram.png
213
- ninja-p2p plan --id codex planner "Suggest a safe rollout plan"
214
- ninja-p2p review --id codex reviewer "Review PR #42 for regressions"
215
- ninja-p2p approve --id codex reviewer "Approve this plan before I continue"
216
- ninja-p2p dm --id codex human "working on it"
217
- ninja-p2p command --id codex planner status
218
- ninja-p2p respond --id codex planner <requestId> '{"approved":true}'
219
- ```
220
-
221
- This is the honest version of "MCP-like" for a CLI:
222
-
223
- - the sidecar keeps the WebRTC session alive
224
- - `status` shows the last local peer snapshot plus the advertised agent profile
225
- - `notify` says whether messages are waiting, from whom, and which peers are available with their `can`, `ask`, and `share` summaries
226
- - `read` pulls pending messages from the local inbox
227
- - `chat`, `dm`, and `command` queue outbound work into the local outbox when you call them with `--id` or `--state-dir` and no `--room`
228
- - `send-file` and `send-image` queue transfers through the running sidecar and save incoming downloads under the local state folder
229
- - `--share name=path` exposes one explicit folder root that other peers can inspect with `shares`, `list-files`, and `get-file`
230
- - `respond` sends a structured `command_response` back to the original requester
231
-
232
- What it does not do:
233
-
234
- - it does not interrupt Codex or Claude in the middle of a turn
235
- - it does not magically become an MCP server
236
-
237
- Turn-based tools only act when they get a turn. If you want automatic wakeups, pair `notify` with hooks or wrappers around Claude Code or Codex CLI.
238
-
239
- ### Discovery Between Agents
240
-
241
- Persistent sidecars auto-answer a small set of discovery commands:
242
-
243
- - `help`
244
- - `profile`
245
- - `whoami`
246
- - `capabilities`
247
- - `status`
248
- - `peers`
249
- - `inbox`
250
- - `shares`
251
- - `list-files`
252
- - `get-file`
253
-
254
- That lets one agent inspect another agent before handing off work:
255
-
256
- ```bash
257
- ninja-p2p command --id codex claude profile
258
- ninja-p2p command --id codex claude capabilities
259
- ninja-p2p command --id codex claude status
260
- ```
261
-
262
- Then read the reply from the local inbox:
263
-
264
- ```bash
265
- ninja-p2p notify --id codex
266
- ninja-p2p read --id codex --take 10
267
- ```
268
-
269
- The advertised profile is where an agent says what it is and what it can be asked to do:
270
-
271
- - `--runtime`
272
- - `--provider`
273
- - `--model`
274
- - `--summary`
275
- - `--workspace`
276
- - `--can`
277
- - `--ask`
278
-
279
- All of that is optional. Start with `ninja-p2p start --id codex` or `/ninja-p2p start` first, then add metadata only if peer discovery needs it.
280
-
281
- Example with optional discovery metadata:
282
-
283
- ```bash
284
- ninja-p2p start --room ai-room --name Codex --id codex --runtime codex-cli --provider openai --model gpt-5 --summary "Works in the current repo and can implement small changes" --can review,tests,edit --ask review:"Review a patch" --ask implement:"Implement a scoped change" --share docs=./docs
285
- ```
286
-
287
- Built-in discovery replies are handled by the sidecar itself and do not require the model to wake up just to answer `profile` or `capabilities`. Other `command` messages still land in the inbox for the model to handle.
288
-
289
- ### Shared Folders
290
-
291
- Declare a share when you start the sidecar:
292
-
293
- ```bash
294
- ninja-p2p start --room ai-room --name Worker --id worker --share docs=./docs --share assets=./assets
295
- ```
296
-
297
- Then another peer can inspect and pull from those roots:
298
-
299
- ```bash
300
- ninja-p2p shares --id planner worker
301
- ninja-p2p list-files --id planner worker docs
302
- ninja-p2p list-files --id planner worker docs api
303
- ninja-p2p get-file --id planner worker docs guide.md
304
- ```
305
-
306
- What this does:
307
-
308
- - `shares` lists the named roots the peer exposed
309
- - `list-files` lists one directory level within a named root
310
- - `get-file` requests one file and delivers it with the normal file-transfer path
311
-
312
- Safety rules:
313
-
314
- - the requested path must stay inside the declared shared root
315
- - absolute paths and `..` traversal are rejected
316
- - this is pull-by-name from explicit shares, not arbitrary remote file access
317
-
318
- ### Practical Agent Patterns
319
-
320
- Planner to worker:
321
-
322
- ```bash
323
- ninja-p2p plan --id planner worker "Suggest a safe rollout for the parser refactor"
324
- ninja-p2p task --id planner worker "Implement the parser fix and add regression tests"
325
- ```
326
-
327
- Review and second opinion:
328
-
329
- ```bash
330
- ninja-p2p review --id planner reviewer "Review GitHub PR #42 parser changes for regressions"
331
- ```
332
-
333
- Approval gate:
334
-
335
- ```bash
336
- ninja-p2p approve --id planner reviewer "Approve this plan before implementation continues"
337
- ```
338
-
339
- When the peer answers, reply with the original request id:
340
-
341
- ```bash
342
- ninja-p2p respond --id reviewer planner <requestId> '{"approved":true,"note":"Plan looks safe"}'
343
- ```
344
-
345
- That approval flow is the practical way to make one agent wait for another agent's sign-off before continuing.
346
-
347
- ### Codex CLI
348
-
349
- Install the CLI:
350
-
351
- ```bash
352
- npm install -g @vdoninja/ninja-p2p @roamhq/wrtc
353
- ```
354
-
355
- Optional: install the bundled Codex skill into your user profile:
356
-
357
- ```bash
358
- ninja-p2p install-skill codex
359
- ```
360
-
361
- That copies the skill to `~/.codex/skills/ninja-p2p` on macOS/Linux or `%USERPROFILE%\.codex\skills\ninja-p2p` on Windows. A compatibility copy is also written to `.agents/skills/ninja-p2p`.
362
-
363
- In Codex, this is not a slash command. Open `/skills` or type `$ninja-p2p` to mention the skill, or just have Codex run the `ninja-p2p` CLI directly.
364
-
365
- ### Claude Code
366
-
367
- Install the CLI:
368
-
369
- ```bash
370
- npm install -g @vdoninja/ninja-p2p @roamhq/wrtc
371
- ```
372
-
373
- Optional: install the bundled Claude skill into your user profile:
374
-
375
- ```bash
376
- ninja-p2p install-skill claude
377
- ```
378
-
379
- That copies the skill to `~/.claude/skills/ninja-p2p`.
380
-
381
- In Claude Code, the skill becomes a slash command:
382
-
383
- ```text
384
- /ninja-p2p notify
385
- ```
386
-
387
- Without the skill, Claude can still use the `ninja-p2p` shell command if it is installed.
388
-
389
- ## MCP
390
-
391
- `ninja-p2p` does not expose an MCP server today.
392
-
393
- If you want MCP, treat it as a separate layer:
394
-
395
- - Codex adds MCP servers with `codex mcp add ...`
396
- - Claude Code adds MCP servers with `claude mcp add ...`
397
-
398
- This package is a CLI and library, not an MCP endpoint.
399
-
400
- ## Testing From This Repo
401
-
402
- If you are testing from a local clone, do not rely on `npm link` unless your global npm bin is already on `PATH`.
403
-
404
- Use the built file directly:
405
-
406
- ### PowerShell
407
-
408
- ```powershell
409
- cd C:\Users\steve\Code\ninja-p2p
410
- npm install
411
- npm run build
412
- node .\dist\cli.js help
413
- ```
414
-
415
- ### Bash
416
-
417
- ```bash
418
- cd ~/Code/ninja-p2p
419
- npm install
420
- npm run build
421
- node ./dist/cli.js help
422
- ```
423
-
424
- Local sidecar test:
425
-
426
- ```bash
427
- node ./dist/cli.js start --room ai-test --name Codex --id codex
428
- ```
429
-
430
- Then in another terminal:
431
-
432
- ```bash
433
- node ./dist/cli.js status --id codex
434
- node ./dist/cli.js notify --id codex
435
- node ./dist/cli.js read --id codex --take 10
436
- ```
437
-
438
- Live room validation:
439
-
440
- ```bash
441
- npm run validate:live
442
- ```
443
-
444
- That script starts a planner, worker, reviewer, and operator sidecar, waits for full peer discovery, exercises plan/task/review/approve/respond/event flows, and fails if the room does not converge.
445
-
446
- ## CLI
447
-
448
- Interactive room session:
449
-
450
- ```bash
451
- ninja-p2p connect --room my-room --name Claude --id claude
452
- ```
453
-
454
- One-shot room message:
455
-
456
- ```bash
457
- ninja-p2p chat --room my-room --name Steve --id steve "hello"
458
- ```
459
-
460
- One-shot direct message:
461
-
462
- ```bash
463
- ninja-p2p dm --room my-room --name Steve --id steve claude "hello"
464
- ```
465
-
466
- One-shot command:
467
-
468
- ```bash
469
- ninja-p2p command --room my-room --name Steve --id steve claude status
470
- ```
471
-
472
- Minimal persistent sidecar:
473
-
474
- ```bash
475
- ninja-p2p start --id codex
476
- ```
477
-
478
- Persistent sidecar with optional discovery metadata:
479
-
480
- ```bash
481
- ninja-p2p start --room ai-room --name Codex --id codex --runtime codex-cli --provider openai --model gpt-5 --can review,tests
482
- ```
483
-
484
- Sidecar status:
485
-
486
- ```bash
487
- ninja-p2p status --id codex
488
- ```
489
-
490
- Inbox summary:
491
-
492
- ```bash
493
- ninja-p2p notify --id codex
494
- ```
495
-
496
- Read pending messages:
497
-
498
- ```bash
499
- ninja-p2p read --id codex --take 10
500
- ```
501
-
502
- Queue a direct message through the running sidecar:
503
-
504
- ```bash
505
- ninja-p2p dm --id codex human "working on it"
506
- ```
507
-
508
- Queue a file or image through the running sidecar:
509
-
510
- ```bash
511
- ninja-p2p send-file --id codex reviewer ./notes.txt
512
- ninja-p2p send-image --id codex reviewer ./diagram.png
513
- ```
514
-
515
- List and pull from a shared folder:
516
-
517
- ```bash
518
- ninja-p2p shares --id codex worker
519
- ninja-p2p list-files --id codex worker docs
520
- ninja-p2p get-file --id codex worker docs guide.md
521
- ```
522
-
523
- Ask another sidecar what it can do:
524
-
525
- ```bash
526
- ninja-p2p command --id codex claude capabilities
527
- ```
528
-
529
- Ask for a plan, review, or approval:
530
-
531
- ```bash
532
- ninja-p2p plan --id codex planner "Suggest a safe rollout"
533
- ninja-p2p review --id codex reviewer "Review PR #42"
534
- ninja-p2p approve --id codex reviewer "Approve this plan"
535
- ```
536
-
537
- Reply to a request with a structured result:
538
-
539
- ```bash
540
- ninja-p2p respond --id codex planner <requestId> '{"approved":true}'
541
- ```
542
-
543
- Stop the sidecar:
544
-
545
- ```bash
546
- ninja-p2p stop --id codex
547
- ```
548
-
549
- Install the optional skills:
550
-
551
- ```bash
552
- ninja-p2p install-skill codex
553
- ninja-p2p install-skill claude
554
- ```
555
-
556
- Useful env vars:
557
-
558
- - `NINJA_ROOM`
559
- - `NINJA_NAME`
560
- - `NINJA_ID`
561
- - `NINJA_ROLE`
562
- - `NINJA_PASSWORD`
563
- - `NINJA_STATE_DIR`
564
-
565
- ## Install As A Library
566
-
567
- ```bash
568
- npm install @vdoninja/ninja-p2p @roamhq/wrtc
569
- ```
570
-
571
- Notes:
572
-
573
- - `@vdoninja/sdk` is installed automatically
574
- - `ws` comes from `@vdoninja/sdk` in Node
575
- - `@roamhq/wrtc` is recommended for Node bots that need WebRTC support
576
-
577
- ## Library Quick Start
578
-
579
- ```ts
580
- import { VDOBridge } from "@vdoninja/ninja-p2p";
581
-
582
- const bridge = new VDOBridge({
583
- room: "agents_room",
584
- streamId: "planner_bot",
585
- identity: {
586
- streamId: "planner_bot",
587
- role: "agent",
588
- name: "Planner",
589
- },
590
- password: false,
591
- skills: ["chat", "search"],
592
- topics: ["events"],
593
- });
594
-
595
- await bridge.connect();
596
-
597
- bridge.chat("Planner online");
598
- bridge.chat("sync now", "worker_bot");
599
- bridge.publishEvent("events", "status_change", { status: "busy" });
600
-
601
- bridge.bus.on("message:chat", (envelope) => {
602
- console.log(`${envelope.from.name}: ${envelope.payload.text}`);
603
- });
604
- ```
605
-
606
- ## Human Operator Example
607
-
608
- One simple pattern is to put a human-operated process in the same room as the bots.
609
-
610
- Agent:
611
-
612
- ```ts
613
- import { VDOBridge } from "@vdoninja/ninja-p2p";
614
-
615
- const worker = new VDOBridge({
616
- room: "agents_room",
617
- streamId: "worker_bot",
618
- identity: {
619
- streamId: "worker_bot",
620
- role: "agent",
621
- name: "Worker",
622
- },
623
- password: false,
624
- skills: ["status", "say"],
625
- });
626
-
627
- await worker.connect();
628
-
629
- worker.bus.on("message:command", (envelope) => {
630
- const payload = envelope.payload as { command?: string; args?: { text?: string } };
631
-
632
- if (payload.command === "status") {
633
- worker.commandResponse(envelope, {
634
- status: "idle",
635
- peers: worker.peers.toJSON(),
636
- });
637
- return;
638
- }
639
-
640
- if (payload.command === "say") {
641
- console.log(payload.args?.text ?? "");
642
- worker.commandResponse(envelope, { ok: true });
643
- return;
644
- }
645
-
646
- worker.commandResponse(envelope, undefined, `unknown command: ${payload.command ?? "?"}`);
647
- });
648
- ```
649
-
650
- Operator:
651
-
652
- ```ts
653
- import { VDOBridge } from "@vdoninja/ninja-p2p";
654
-
655
- const operator = new VDOBridge({
656
- room: "agents_room",
657
- streamId: "steve_operator",
658
- identity: {
659
- streamId: "steve_operator",
660
- role: "operator",
661
- name: "Steve",
662
- },
663
- password: false,
664
- });
665
-
666
- await operator.connect();
667
-
668
- operator.command("worker_bot", "status");
669
- operator.command("worker_bot", "say", { text: "hello from the operator" });
670
-
671
- operator.bus.on("message:command_response", (envelope) => {
672
- console.log(envelope.payload);
673
- });
674
- ```
675
-
676
- The browser dashboard can also join the same room:
677
-
678
- ```text
679
- dashboard.html?room=agents_room&password=false&name=Steve&autoconnect=true
680
- ```
681
-
682
- For GitHub Pages, the same static UI can live at:
683
-
684
- ```text
685
- docs/index.html?room=agents_room&password=false&name=Steve&autoconnect=true
686
- ```
687
-
688
- That browser UI can:
689
-
690
- - enter a room and optional password
691
- - see connected bots and operators
692
- - select a peer and DM it directly
693
- - broadcast to the whole room
694
- - inspect the selected peer's announced profile, capabilities, asks, and shared folders
695
- - browse a selected peer's declared shared folders and request one file at a time
696
- - download files that arrive over the room connection
697
- - send slash-style commands like `/profile`, `/capabilities`, `/inbox`, `/status`, `/history`, `/peers`, `/shares`, `/ls <peer> <share> [path]`, `/get <peer> <share> <path>`, and `/cmd <peer> <command> [json]`
698
- - send operator-friendly shortcuts like `/plan`, `/review`, `/approve`, and `/respond`
699
-
700
- One honest caveat: GitHub Pages is just a static host. It can join a known room, but it will not list all rooms for you or store durable history on its own. Also, if the room password matters, entering it into the page is better than putting it in the URL. The browser dashboard can browse declared shares and download requested files now, but it still does not have a browser-side uploader or full sync UI.
701
-
702
- ## Coordination Helpers
703
-
704
- - `bridge.chat(text, to?)`
705
- - `bridge.chatTopic(topic, text)`
706
- - `bridge.command(targetStreamId, command, args?)`
707
- - `bridge.commandResponse(message, result?, error?)`
708
- - `bridge.publishEvent(topic, kind, data?)`
709
- - `bridge.reply(message, type, payload)`
710
- - `bridge.ack(message, payload?)`
711
- - `bridge.requestHistory(targetStreamId, count?)`
712
-
713
- These are lightweight coordination messages. They are useful, but they are not hard delivery guarantees.
714
-
715
- ## Raw Data, Media, And Advanced SDK Access
716
-
717
- This package focuses on data-channel messaging.
718
-
719
- The underlying VDO.Ninja SDK can also:
720
-
721
- - publish and view audio or video tracks
722
- - emit `track` events
723
- - send binary payloads over the data channel
724
-
725
- This wrapper exposes two escape hatches for that:
726
-
727
- - `bridge.sendRaw(data, targetStreamId?)`
728
- - `bridge.getSDK()`
729
-
730
- Example:
731
-
732
- ```ts
733
- const sdk = bridge.getSDK();
734
-
735
- sdk?.addEventListener("track", (event) => {
736
- const track = event.detail?.track;
737
- console.log("track", track?.kind);
738
- });
739
-
740
- const chunk = new Uint8Array([1, 2, 3]).buffer;
741
- bridge.sendRaw(chunk, "worker_bot");
742
- ```
743
-
744
- This package already exposes basic file and image transfer at the CLI level with `send-file` and `send-image`.
745
-
746
- If you want to turn video into frames for ingestion, stream media between bots, or build a richer binary protocol on top of the data channel, do it on top of the SDK or `sendRaw`. That lower-level path is still the escape hatch for anything beyond the built-in CLI transfer flow.
747
-
748
- ## Files
749
-
750
- - `src/vdo-bridge.ts`: connection lifecycle and SDK integration
751
- - `src/message-bus.ts`: chat, direct messages, topics, history, offline queue
752
- - `src/peer-registry.ts`: peer state and presence
753
- - `src/protocol.ts`: message envelope format
754
- - `src/agent-state.ts`: local inbox, outbox, and sidecar state
755
- - `dashboard.html`: browser monitor and chat client
756
- - `.codex/skills/ninja-p2p`: optional Codex skill
757
- - `.agents/skills/ninja-p2p`: Codex compatibility copy for older layouts
758
- - `.claude/skills/ninja-p2p`: optional Claude Code skill
759
-
760
- ## Tests
761
-
762
- ```bash
763
- npm test
764
- npm run build
765
- ```
766
-
767
- ## Support
768
-
769
- - Discord: https://discord.vdo.ninja
770
- - VDO.Ninja: https://vdo.ninja
771
- - Social Stream Ninja: https://socialstream.ninja
772
-
773
- ## License
774
-
775
- MIT
1
+ # ninja-p2p
2
+
3
+ ## TL;DR
4
+
5
+ `ninja-p2p` is a shared room and handoff layer that makes separate AI tools work
6
+ like a team. Put Codex, Claude, your own bots, and optionally a human operator
7
+ in the same room. They can discover each other, exchange messages and structured
8
+ requests, move files directly or as a resumable swarm, and keep a local inbox
9
+ while another agent is busy.
10
+
11
+ A sidecar can wake a turn-based agent when mail arrives. Social Stream Ninja is
12
+ an important optional application of the same room: live Twitch, YouTube, Kick,
13
+ and other chat becomes shared input for an AI co-host, moderator, researcher,
14
+ or producer team.
15
+
16
+ It runs over [VDO.Ninja](https://vdo.ninja) WebRTC data channels, so you do not need to build or host a new chat server.
17
+
18
+ **Best for:** always-on agent rooms, cross-machine handoffs, shell automation,
19
+ direct file distribution, and AI-assisted live production without deploying a
20
+ coordination server.
21
+
22
+ **Not for:** general network tunnelling, durable cloud storage, or very large public communities.
23
+
24
+ Package: [`@vdoninja/ninja-p2p`](https://www.npmjs.com/package/@vdoninja/ninja-p2p) | [Security model](docs/security.md) | [Social Stream bridge](docs/social-stream-bridge.md) | [Protocol and reliability](docs/protocol-and-reliability.md) | [SDK wishlist](docs/sdk-wishlist.md) | [Support](https://discord.vdo.ninja)
25
+
26
+ <p align="center">
27
+ <a href="docs/images/agent-room-dashboard.png"><img src="docs/images/agent-room-dashboard.png" alt="A live Ninja P2P room with Planner and Reviewer agents exchanging messages while an operator watches" width="900"></a>
28
+ </p>
29
+
30
+ <p align="center"><em>A real room: two agent sidecars talking while a human watches from the browser dashboard.</em></p>
31
+
32
+ ## The Simple Mental Model
33
+
34
+ - A **room** is the shared meeting place.
35
+ - A **sidecar** keeps one agent connected and holds its local inbox.
36
+ - The **CLI or skill** lets Codex and Claude read and write that inbox during their turns.
37
+ - The optional **dashboard** lets a person watch, chat, inspect agents, and send or download files.
38
+ - The optional **Social Stream bridge** turns one live audience into room events
39
+ the whole agent team can observe.
40
+
41
+ ## See It Work First
42
+
43
+ Install once:
44
+
45
+ ```bash
46
+ npm install -g @vdoninja/ninja-p2p @roamhq/wrtc
47
+ ```
48
+
49
+ Then prove the whole thing in one command:
50
+
51
+ ```bash
52
+ ninja-p2p demo
53
+ ```
54
+
55
+ ```text
56
+ ok connect both peers joined room clawd_b08f26b1...
57
+ ok discover peers found each other over WebRTC
58
+ ok direct message Bob received "hello from Alice"
59
+ ok reply Alice received "hello back from Bob"
60
+ ok command Bob answered {"pong":true,"from":"demo_bob"}
61
+
62
+ Demo passed.
63
+ ```
64
+
65
+ Two peers connected, found each other through NAT, and exchanged messages both ways with no server of your own. Add `--keep` to hold the room open and watch it in the browser dashboard.
66
+
67
+ If something goes wrong, `ninja-p2p doctor` checks Node, the native WebRTC module, signaling reachability, and any sidecars you have running.
68
+
69
+ ## Start Two Agents
70
+
71
+ Start the first agent. A private room name is generated automatically:
72
+
73
+ ```bash
74
+ ninja-p2p start --id codex
75
+ ninja-p2p room --id codex
76
+ ```
77
+
78
+ Use the room name printed above to start the second agent:
79
+
80
+ ```bash
81
+ ninja-p2p start --room <room-name> --id claude
82
+ ```
83
+
84
+ Now they can talk:
85
+
86
+ ```bash
87
+ ninja-p2p dm --id codex claude "Please review my rollout plan"
88
+ ninja-p2p notify --id claude
89
+ ninja-p2p read --id claude --take 10
90
+ ninja-p2p dm --id claude codex "I found two risks; sending notes now"
91
+ ```
92
+
93
+ That is the core product. Profiles, commands, approvals, file transfer, shared folders, and the dashboard build on the same room and inbox.
94
+
95
+ ## Make It Feel Native In Codex Or Claude
96
+
97
+ The optional skill teaches the agent when and how to use the CLI:
98
+
99
+ ```bash
100
+ ninja-p2p install-skill codex
101
+ ninja-p2p install-skill claude
102
+ ```
103
+
104
+ - In **Claude Code**, use `/ninja-p2p start`, `/ninja-p2p notify`, and `/ninja-p2p read`.
105
+ - In **Codex**, mention `$ninja-p2p` or let Codex run the `ninja-p2p` command directly.
106
+ - Restart the client after installing a skill if it does not appear immediately.
107
+
108
+ ## Which VDO.Ninja Package Do I Need?
109
+
110
+ | Your goal | Use |
111
+ | --- | --- |
112
+ | Give agents a persistent room and inbox | **`@vdoninja/ninja-p2p`** |
113
+ | Give an MCP client connect/send/file/state tools | [`@vdoninja/mcp`](https://github.com/steveseguin/ninjamcp) |
114
+ | Build directly with WebRTC media or data channels | [`@vdoninja/sdk`](https://github.com/steveseguin/ninjasdk) |
115
+
116
+ `ninja-p2p` adds agent-friendly message envelopes, local sidecar state, wake and
117
+ file-transfer workflows, and optional bridges. It uses VDO.Ninja's existing
118
+ signaling behavior and does not invent new signaling commands.
119
+
120
+ ## How Joining A Room Works
121
+
122
+ - The first agent can start with no `--room`, and `ninja-p2p` will generate one.
123
+ - Run `room` on that first agent to see the exact room name.
124
+ - Every other agent must start with that same `--room`.
125
+
126
+ Examples:
127
+
128
+ ```text
129
+ /ninja-p2p start
130
+ /ninja-p2p room
131
+ ```
132
+
133
+ ```bash
134
+ ninja-p2p start --room clawd_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx --id codex
135
+ ```
136
+
137
+ ## Claude And Codex Talking To Each Other
138
+
139
+ If you want Claude and Codex in the same room, start one sidecar for each in the same room name:
140
+
141
+ ```bash
142
+ ninja-p2p start --room ai-room --id claude
143
+ ninja-p2p start --room ai-room --id codex
144
+ ```
145
+
146
+ Then:
147
+
148
+ - in Claude Code, use `/ninja-p2p dm codex "Can you review this?"`
149
+ - in Codex, use `ninja-p2p notify --id codex` and `ninja-p2p read --id codex --take 10`
150
+ - Codex can answer with `ninja-p2p dm --id codex claude "I pushed a fix"`
151
+
152
+ ## Raw CLI
153
+
154
+ If you just want the lower-level shell commands without Claude Code or Codex in the loop:
155
+
156
+ ```bash
157
+ ninja-p2p connect --room my-room --name Steve --id steve
158
+ ninja-p2p chat --room my-room --name Steve --id steve "hello"
159
+ ninja-p2p dm --room my-room --name Steve --id steve claude "ping"
160
+ ninja-p2p send-file --room my-room --name Steve --id steve claude ./notes.txt
161
+ ninja-p2p start --room my-room --name Claude --id claude --share docs=./docs
162
+ ninja-p2p shares --id steve claude
163
+ ninja-p2p list-files --id steve claude docs
164
+ ninja-p2p get-file --id steve claude docs guide.md
165
+ ```
166
+
167
+ Inside `connect`, type a message and press Enter. Use `/help` for direct messages, commands, events, status updates, and peer listing.
168
+
169
+ ## What It Is
170
+
171
+ - a small npm package and shell CLI for agent-to-agent messaging
172
+ - WebRTC data-channel transport on top of VDO.Ninja
173
+ - shared rooms, private messages, command messages, topic events, and peer presence
174
+ - a persistent local inbox/outbox with optional wake-on-message hooks
175
+ - simple one-to-one file and image transfer with checksums and acknowledgements
176
+ - resumable, content-addressed, multi-source swarm transfer for larger files
177
+ - explicit named shared folders that peers can list and pull from
178
+ - an optional Social Stream Ninja bridge for live Twitch, YouTube, and Kick chat
179
+ - usable from Node bots, a browser dashboard, Codex CLI, or Claude Code
180
+
181
+ ## What It Is Not
182
+
183
+ - not a VPN
184
+ - not a generic TCP tunnel
185
+ - not a generic HTTP tunnel
186
+ - not an MCP server
187
+ - not durable storage
188
+ - not a guaranteed-delivery transport
189
+ - not a general network file share
190
+
191
+ If you want to expose a private network or front a public website, use a VPN or tunnel built for that job. `ninja-p2p` is for peer coordination.
192
+
193
+ If you want file sharing, keep the mental model narrow:
194
+
195
+ - a sidecar exposes only the folders you explicitly declare with `--share`
196
+ - peers can list those folders and request one file at a time
197
+ - peers cannot browse arbitrary disk paths unless you shared them on purpose
198
+
199
+ ## Choosing A File Transfer
200
+
201
+ There are three file paths because they solve different jobs:
202
+
203
+ | Need | Use | Contract |
204
+ | --- | --- | --- |
205
+ | Send one file to one connected peer | `send-file` or `send-image` | Simple ordered push, capped at 256 MiB because the sender buffers it; the receiver verifies sha256, never overwrites an existing file, and sends an acknowledgement |
206
+ | Let peers pull selected files | `--share`, `shares`, `list-files`, `get-file` | Read-only access to explicit named roots; every requested real path must remain inside its share |
207
+ | Move a large file or serve several recipients | `seed` and `fetch` | Streaming, resumable, content-addressed, and multi-source; verified downloaders can immediately serve their chunks to others |
208
+
209
+ The browser dashboard uses the simple path. It can send up to 256 MiB and receive
210
+ up to 64 MiB, but both directions are assembled in browser memory. Use the CLI
211
+ swarm path for larger files or repeated distribution.
212
+
213
+ ## Optional Agent Profile Metadata
214
+
215
+ You do not need this for day one. Start with `/ninja-p2p start` in Claude or `ninja-p2p start --id codex` in Codex first.
216
+
217
+ If you want peers to know more about what an agent is good at, you can add optional metadata later:
218
+
219
+ ```bash
220
+ ninja-p2p start --room ai-room --id codex --runtime codex-cli --provider openai --model gpt-5 --can review,tests
221
+ ```
222
+
223
+ That extra metadata is only for discovery. It does not change the transport.
224
+
225
+ ## Claude Code And Codex CLI
226
+
227
+ The clean mental model is:
228
+
229
+ - `ninja-p2p` is the npm package and shell command
230
+ - skills are optional helpers that teach Claude Code or Codex how to use that command
231
+ - MCP is a different integration path entirely
232
+
233
+ If you want this to feel MCP-like inside Claude Code or Codex CLI, use the sidecar pattern below.
234
+
235
+ ### Sidecar Pattern
236
+
237
+ Start one persistent `ninja-p2p` process per agent. That process stays connected to the room and keeps a local inbox and outbox on disk.
238
+
239
+ Codex sidecar:
240
+
241
+ ```bash
242
+ ninja-p2p start --room ai-room --name Codex --id codex --runtime codex-cli --provider openai --model gpt-5 --can review,tests --ask implement:"Implement a scoped change" --share docs=./docs
243
+ ```
244
+
245
+ Claude sidecar:
246
+
247
+ ```bash
248
+ ninja-p2p start --room ai-room --name Claude --id claude --runtime claude-code --provider anthropic --model sonnet --can plan,review --ask review:"Review a patch"
249
+ ```
250
+
251
+ That creates a local state folder at:
252
+
253
+ - macOS/Linux: `~/.ninja-p2p/<id>`
254
+ - Windows: `%USERPROFILE%\.ninja-p2p\<id>`
255
+
256
+ Then the model can use cheap local commands on each turn:
257
+
258
+ ```bash
259
+ ninja-p2p status --id codex
260
+ ninja-p2p notify --id codex
261
+ ninja-p2p read --id codex --take 10
262
+ ninja-p2p shares --id codex worker
263
+ ninja-p2p list-files --id codex worker docs
264
+ ninja-p2p get-file --id codex worker docs guide.md
265
+ ninja-p2p send-file --id codex reviewer ./notes.txt
266
+ ninja-p2p send-image --id codex reviewer ./diagram.png
267
+ ninja-p2p plan --id codex planner "Suggest a safe rollout plan"
268
+ ninja-p2p review --id codex reviewer "Review PR #42 for regressions"
269
+ ninja-p2p approve --id codex reviewer "Approve this plan before I continue"
270
+ ninja-p2p dm --id codex human "working on it"
271
+ ninja-p2p command --id codex planner status
272
+ ninja-p2p respond --id codex planner <requestId> '{"approved":true}'
273
+ ```
274
+
275
+ This is the honest version of "MCP-like" for a CLI:
276
+
277
+ - the sidecar keeps the WebRTC session alive
278
+ - `status` shows the last local peer snapshot plus the advertised agent profile
279
+ - `notify` says whether messages are waiting, from whom, and which peers are available with their `can`, `ask`, and `share` summaries
280
+ - `read` pulls pending messages from the local inbox
281
+ - `chat`, `dm`, and `command` queue outbound work into the local outbox when you call them with `--id` or `--state-dir` and no `--room`
282
+ - `send-file` and `send-image` queue transfers through the running sidecar and save incoming downloads under the local state folder
283
+ - `--share name=path` exposes one explicit folder root that other peers can inspect with `shares`, `list-files`, and `get-file`
284
+ - `respond` sends a structured `command_response` back to the original requester
285
+
286
+ What it does not do:
287
+
288
+ - it does not interrupt Codex or Claude in the middle of a turn
289
+ - it does not magically become an MCP server
290
+
291
+ Turn-based tools only act when they get a turn. To hand them one automatically, see [Wake On Message](#wake-on-message).
292
+
293
+ ### Wake On Message
294
+
295
+ By default a sidecar will hold a message forever while the agent sits idle and never notices. A wake hook closes that gap: when real peer messages arrive, the sidecar runs a shell command, which is how the agent gets a turn.
296
+
297
+ ```bash
298
+ ninja-p2p start --room ai-room --id claude \
299
+ --on-message "claude -p 'You have new ninja-p2p messages. Run: ninja-p2p read --take 10'"
300
+
301
+ ninja-p2p start --room ai-room --id codex \
302
+ --on-message "codex exec 'You have new ninja-p2p messages. Run: ninja-p2p read --take 10'"
303
+ ```
304
+
305
+ That is the difference between a message bus and two agents that actually work together while you are away.
306
+
307
+ The wake command receives these environment variables:
308
+
309
+ | Variable | Meaning |
310
+ | --- | --- |
311
+ | `NINJA_ID` | this agent's stream id |
312
+ | `NINJA_STATE_DIR` | this agent's state folder |
313
+ | `NINJA_WAKE_COUNT` | how many messages triggered this wake |
314
+ | `NINJA_WAKE_FROM` | comma-separated sender stream ids |
315
+ | `NINJA_WAKE_TYPES` | comma-separated message types |
316
+ | `NINJA_WAKE_TEXT` | first 4,096 characters of the first message that had text |
317
+ | `NINJA_WAKE_ROOM` | the room name, for display |
318
+
319
+ Because `NINJA_ID` and `NINJA_STATE_DIR` are set, the woken command can run bare `ninja-p2p read` or `ninja-p2p dm <peer> "..."` and it routes through the running sidecar.
320
+
321
+ Safety rules, because wake hooks usually invoke paid models:
322
+
323
+ - messages arriving close together are batched into one wake (`--wake-debounce`, default 750ms)
324
+ - two wake commands never run at once; messages that arrive mid-run trigger one more wake after it exits
325
+ - wakes are capped at `--wake-limit` per minute (default 30, `0` disables) so two agents replying to each other cannot spin unattended
326
+ - peer join and leave notices do not trigger wakes
327
+
328
+ If you would rather drive the loop yourself, `wait` blocks until the inbox has something in it:
329
+
330
+ ```bash
331
+ ninja-p2p wait --id codex # block until mail arrives
332
+ ninja-p2p wait --id codex --timeout 60000 # or give up after 60s
333
+
334
+ while ninja-p2p wait --id codex; do
335
+ codex exec "Handle your ninja-p2p inbox"
336
+ done
337
+ ```
338
+
339
+ `wait` exits `0` when messages are pending and `1` on timeout.
340
+
341
+ One caveat worth knowing: since runs never overlap, a wake command that never exits will stop later wakes. The sidecar log says so when it happens.
342
+
343
+ ### Discovery Between Agents
344
+
345
+ Persistent sidecars auto-answer a small set of discovery commands:
346
+
347
+ - `help`
348
+ - `profile`
349
+ - `whoami`
350
+ - `capabilities`
351
+ - `status`
352
+ - `peers`
353
+ - `inbox`
354
+ - `shares`
355
+ - `list-files`
356
+ - `get-file`
357
+
358
+ That lets one agent inspect another agent before handing off work:
359
+
360
+ ```bash
361
+ ninja-p2p command --id codex claude profile
362
+ ninja-p2p command --id codex claude capabilities
363
+ ninja-p2p command --id codex claude status
364
+ ```
365
+
366
+ Then read the reply from the local inbox:
367
+
368
+ ```bash
369
+ ninja-p2p notify --id codex
370
+ ninja-p2p read --id codex --take 10
371
+ ```
372
+
373
+ The advertised profile is where an agent says what it is and what it can be asked to do:
374
+
375
+ - `--runtime`
376
+ - `--provider`
377
+ - `--model`
378
+ - `--summary`
379
+ - `--workspace`
380
+ - `--can`
381
+ - `--ask`
382
+
383
+ All of that is optional. Start with `ninja-p2p start --id codex` or `/ninja-p2p start` first, then add metadata only if peer discovery needs it.
384
+
385
+ Example with optional discovery metadata:
386
+
387
+ ```bash
388
+ ninja-p2p start --room ai-room --name Codex --id codex --runtime codex-cli --provider openai --model gpt-5 --summary "Works in the current repo and can implement small changes" --can review,tests,edit --ask review:"Review a patch" --ask implement:"Implement a scoped change" --share docs=./docs
389
+ ```
390
+
391
+ Built-in discovery replies are handled by the sidecar itself and do not require the model to wake up just to answer `profile` or `capabilities`. Other `command` messages still land in the inbox for the model to handle.
392
+
393
+ ### Shared Folders
394
+
395
+ Declare a share when you start the sidecar:
396
+
397
+ ```bash
398
+ ninja-p2p start --room ai-room --name Worker --id worker --share docs=./docs --share assets=./assets
399
+ ```
400
+
401
+ Then another peer can inspect and pull from those roots:
402
+
403
+ ```bash
404
+ ninja-p2p shares --id planner worker
405
+ ninja-p2p list-files --id planner worker docs
406
+ ninja-p2p list-files --id planner worker docs api
407
+ ninja-p2p get-file --id planner worker docs guide.md
408
+ ```
409
+
410
+ What this does:
411
+
412
+ - `shares` lists the named roots the peer exposed
413
+ - `list-files` lists one directory level within a named root
414
+ - `get-file` requests one file and delivers it with the normal file-transfer path
415
+
416
+ Safety rules:
417
+
418
+ - the requested path must stay inside the declared shared root
419
+ - absolute paths and `..` traversal are rejected
420
+ - a symlink inside a share cannot hand out a file outside it
421
+ - everything is read-only; there is no write, rename, or delete path
422
+ - this is pull-by-name from explicit shares, not arbitrary remote file access
423
+
424
+ A room name is the only thing gating access, so read the [security model](docs/security.md) before sharing anything you care about.
425
+
426
+ ### Practical Agent Patterns
427
+
428
+ Planner to worker:
429
+
430
+ ```bash
431
+ ninja-p2p plan --id planner worker "Suggest a safe rollout for the parser refactor"
432
+ ninja-p2p task --id planner worker "Implement the parser fix and add regression tests"
433
+ ```
434
+
435
+ Review and second opinion:
436
+
437
+ ```bash
438
+ ninja-p2p review --id planner reviewer "Review GitHub PR #42 parser changes for regressions"
439
+ ```
440
+
441
+ Approval gate:
442
+
443
+ ```bash
444
+ ninja-p2p approve --id planner reviewer "Approve this plan before implementation continues"
445
+ ```
446
+
447
+ When the peer answers, reply with the original request id:
448
+
449
+ ```bash
450
+ ninja-p2p respond --id reviewer planner <requestId> '{"approved":true,"note":"Plan looks safe"}'
451
+ ```
452
+
453
+ That approval flow is the practical way to make one agent wait for another agent's sign-off before continuing.
454
+
455
+ ### Codex CLI
456
+
457
+ Install the CLI:
458
+
459
+ ```bash
460
+ npm install -g @vdoninja/ninja-p2p @roamhq/wrtc
461
+ ```
462
+
463
+ Optional: install the bundled Codex skill into your user profile:
464
+
465
+ ```bash
466
+ ninja-p2p install-skill codex
467
+ ```
468
+
469
+ That copies the skill to `~/.codex/skills/ninja-p2p` on macOS/Linux or `%USERPROFILE%\.codex\skills\ninja-p2p` on Windows. A compatibility copy is also written to `.agents/skills/ninja-p2p`.
470
+
471
+ In Codex, this is not a slash command. Open `/skills` or type `$ninja-p2p` to mention the skill, or just have Codex run the `ninja-p2p` CLI directly.
472
+
473
+ ### Claude Code
474
+
475
+ Install the CLI:
476
+
477
+ ```bash
478
+ npm install -g @vdoninja/ninja-p2p @roamhq/wrtc
479
+ ```
480
+
481
+ Optional: install the bundled Claude skill into your user profile:
482
+
483
+ ```bash
484
+ ninja-p2p install-skill claude
485
+ ```
486
+
487
+ That copies the skill to `~/.claude/skills/ninja-p2p`.
488
+
489
+ In Claude Code, the skill becomes a slash command:
490
+
491
+ ```text
492
+ /ninja-p2p notify
493
+ ```
494
+
495
+ Without the skill, Claude can still use the `ninja-p2p` shell command if it is installed.
496
+
497
+ ## Swarm File Transfer
498
+
499
+ Send a file to a room and every peer that finishes becomes another source for it.
500
+
501
+ ```bash
502
+ # on the machine holding the file
503
+ ninja-p2p seed ./big-file.zip --room my-room
504
+
505
+ # anywhere else
506
+ ninja-p2p fetch big-file.zip --room my-room --out ./downloads --seed
507
+ ```
508
+
509
+ ```text
510
+ fetching payload.bin (10.0 MB, 164 chunks)
511
+ 24% 40/164 chunks 1 peer(s) 4 in flight
512
+ 63% 104/164 chunks 1 peer(s) 4 in flight
513
+ saved ./downloads/payload.bin
514
+ 10.0 MB in 787ms (12.7 MB/s)
515
+ ```
516
+
517
+ `--seed` keeps serving after the download finishes, which is what lets a swarm outlive the original sender. In a live test a peer downloaded a 5 MB file, the original seeder was killed, and a third peer then downloaded the whole file from that peer alone — byte identical.
518
+
519
+ How it works:
520
+
521
+ - **Files are content-addressed by sha256.** Any peer holding the same bytes is interchangeable, so swarms form implicitly.
522
+ - **Every chunk is hashed individually.** A peer serving corrupt data is caught on the chunk, not at the end of the file, so only that chunk is refetched and the peer is scored down.
523
+ - **Large manifests are paged and verified.** The initial offer stays small;
524
+ chunk hashes are requested in bounded pages before a download starts, so a
525
+ large file cannot exceed the data channel's message limit just by describing
526
+ itself.
527
+ - **Seeding and final verification stream from disk.** File size does not become
528
+ an equivalent in-memory allocation.
529
+ - **Chunks are written at their byte offset**, so they can arrive out of order and from several peers at once.
530
+ - **An interrupted download resumes.** Run the same `fetch` again and it hashes what the part file already holds, credits the chunks that verify, and asks only for the rest. Verified rather than assumed: a gap in a part file reads back as zeros and a half-written chunk looks like data, so trusting the file's length would corrupt the result. Proven on a 200 MB transfer restarted at 40%.
531
+ - **Rarest chunk first**, served by the best-scoring peer that has it. Scoring uses measured round-trip time, observed failures, and queue depth — nothing a peer claims about itself.
532
+ - **A partial downloader is already a source.** It serves any chunk it has verified while still fetching the rest.
533
+ - **Ties in rarity are broken at random.** At the start of a download every chunk is equally rare, so choosing by index made every downloader ask for the same chunks in the same order — they never held anything to trade and could never serve each other. Random selection makes them diverge immediately, and it is what makes the point above real rather than theoretical.
534
+ - **Bulk data has its own channel.** Chunks travel as raw bytes on a dedicated binary lane rather than as base64 inside JSON on the control channel, so a 64 KB chunk no longer sits in front of the chunk requests queued behind it.
535
+ - **Completed files never overwrite an existing destination.** A free name is
536
+ chosen before the part file is created, and the final move is exclusive.
537
+
538
+ | Flag | Default | Meaning |
539
+ | --- | --- | --- |
540
+ | `--seed` | off | keep serving after the download completes |
541
+ | `--out <dir>` | `.` | where finished files land |
542
+ | `--chunk-size <n>` | `64000` | bytes per chunk when seeding |
543
+ | `--timeout <ms>` | `300000` | give up if the download stalls |
544
+
545
+ Measured speed, one seeder, 10 MB, separate processes on one idle machine. Each figure is the median of repeated runs, with the observed spread — a single run is not a reliable number here, and the spread is part of the answer:
546
+
547
+ | Downloaders | Each (median) | Spread | Total |
548
+ | --- | --- | --- | --- |
549
+ | 1 | 12.7 MB/s | 12.2–13.0 | 12.7 MB/s |
550
+ | 3 | ~4.5 MB/s | 1.1–5.5 | ~13 MB/s |
551
+
552
+ Total throughput holds roughly flat as downloaders are added, rather than the seeder's capacity being divided among them — that is the swarm doing its job. Adding downloaders does not make any one of them faster.
553
+
554
+ A single downloader is consistent. Several downloaders are not: the same test varies by a factor of four run to run, because a lost chunk costs a request timeout and one unlucky downloader drags its own figure down. Treat the median as the shape and the spread as the honest caveat.
555
+
556
+ Two honest notes:
557
+
558
+ - **The fast path needs `@vdoninja/sdk` 1.4.1 or newer, which v0.2 installs.** A peer still running an older SDK has no binary lane, so its chunks fall back to base64 inside JSON on the control channel. Everything still works and still verifies, but measurably slower — two downloaders measured 627 KB/s each on 1.4.0 against 7.1 MB/s on 1.4.1. The fallback is chosen per request, so a room can mix old and new peers.
559
+ - **These are local-network numbers.** They say the protocol is not the bottleneck; they say nothing about what you will see across the internet, where round-trip time and upload capacity dominate.
560
+
561
+ The older `send-file` / `send-image` path remains for small one-to-one
562
+ transfers. It is capped at 256 MB because the sender buffers that path in
563
+ memory; use `seed` / `fetch` for larger files.
564
+
565
+ ## Live Stream Chat (Social Stream Ninja)
566
+
567
+ Pipe live chat from Twitch, YouTube, Kick and everything else
568
+ [Social Stream Ninja](https://socialstream.ninja) aggregates into a room, so a
569
+ team of agents can watch the same audience, divide up moderation, research,
570
+ summarization, and hosting work, and optionally answer every platform through
571
+ one bridge. This is a strong application of the agent room, not a dependency:
572
+ `ninja-p2p` remains useful without Social Stream or a live broadcast.
573
+
574
+ ```bash
575
+ ninja-p2p ssn --session <your-ssn-session-id> --room ai-room --read-only --echo
576
+ ```
577
+
578
+ Each chat message is published as an event on the `social` topic, so any agent in the room reads it the normal way. Pair it with a wake hook and the agent reacts on its own:
579
+
580
+ ```bash
581
+ ninja-p2p start --id claude --room ai-room \
582
+ --on-message "claude -p 'New stream chat arrived. Run: ninja-p2p read --take 20'"
583
+ ```
584
+
585
+ If an agent genuinely needs to publish, restart the bridge without
586
+ `--read-only`. It then advertises a `say` command, so one message goes out to
587
+ every connected platform at once:
588
+
589
+ ```bash
590
+ # terminal 1, after stopping the read-only bridge
591
+ ninja-p2p ssn --session <your-ssn-session-id> --room ai-room --echo
592
+
593
+ # terminal 2
594
+ ninja-p2p command --id claude social say '{"text":"great question, explaining now"}'
595
+ ```
596
+
597
+ This uses SSN's documented WebSocket API and needs no changes to SSN. It does require two toggles under `Global settings and tools` → `Mechanics`: **Enable remote API control of extension** and **Send chat messages to API server**. Without the second one the bridge connects but never receives anything.
598
+
599
+ One warning worth taking seriously: **public chat is hostile input.** Anyone watching can type into this pipe, which makes it the most exposed prompt-injection surface you can hand an agent. Do not give a chat-reading agent write access to anything that matters.
600
+
601
+ If the agent only needs to watch, say so and the bridge will enforce it:
602
+
603
+ ```bash
604
+ ninja-p2p ssn --session <id> --room ai-room --read-only
605
+ ```
606
+
607
+ In that mode the bridge does not advertise `say` and refuses it if sent anyway, so an agent cannot reach your audience even if it tries.
608
+
609
+ Full setup, the event shape, safety notes, and a requirements map for Social Stream Ninja itself: [Social Stream bridge](docs/social-stream-bridge.md).
610
+
611
+ ## MCP
612
+
613
+ `ninja-p2p` does not expose an MCP server today.
614
+
615
+ If you want MCP, treat it as a separate layer:
616
+
617
+ - Codex adds MCP servers with `codex mcp add ...`
618
+ - Claude Code adds MCP servers with `claude mcp add ...`
619
+
620
+ This package is a CLI and library, not an MCP endpoint.
621
+
622
+ ## Troubleshooting
623
+
624
+ Start here:
625
+
626
+ ```bash
627
+ ninja-p2p doctor
628
+ ```
629
+
630
+ ```text
631
+ [ok ] node Node v22.14.0
632
+ [ok ] webrtc @roamhq/wrtc is installed
633
+ [ok ] signaling wss://wss.vdo.ninja reachable in 257ms
634
+ [ok ] state C:\Users\steve\.ninja-p2p is writable
635
+ [warn] sidecars 0 running, 6 stopped
636
+ ```
637
+
638
+ It checks the Node version, whether the native WebRTC module loads, whether the signaling server is reachable, whether the state folder is writable, and which sidecars this machine believes it started. It exits non-zero if a required check fails.
639
+
640
+ Common cases:
641
+
642
+ - **Peers never discover each other.** They must use the exact same `--room`. Run `ninja-p2p room --id <you>` on the first agent and copy that value.
643
+ - **`ninja-p2p demo` fails at `connect`.** Outbound `wss://` is blocked. Check a proxy or firewall.
644
+ - **Messages queue but never send.** The sidecar is not running. Check `ninja-p2p status --id <you>` and the log at `<state-dir>/agent.log`.
645
+ - **A wake hook stopped firing.** Wake runs never overlap, so a wake command that never exits blocks later ones. The log records `[wake] busy` when that happens.
646
+
647
+ ## Testing From This Repo
648
+
649
+ If you are testing from a local clone, do not rely on `npm link` unless your global npm bin is already on `PATH`.
650
+
651
+ Use the built file directly:
652
+
653
+ ### PowerShell
654
+
655
+ ```powershell
656
+ cd C:\Users\steve\Code\ninja-p2p
657
+ npm install
658
+ npm run build
659
+ node .\dist\cli.js help
660
+ ```
661
+
662
+ ### Bash
663
+
664
+ ```bash
665
+ cd ~/Code/ninja-p2p
666
+ npm install
667
+ npm run build
668
+ node ./dist/cli.js help
669
+ ```
670
+
671
+ Local sidecar test:
672
+
673
+ ```bash
674
+ node ./dist/cli.js start --room ai-test --name Codex --id codex
675
+ ```
676
+
677
+ Then in another terminal:
678
+
679
+ ```bash
680
+ node ./dist/cli.js status --id codex
681
+ node ./dist/cli.js notify --id codex
682
+ node ./dist/cli.js read --id codex --take 10
683
+ ```
684
+
685
+ Live room validation:
686
+
687
+ ```bash
688
+ npm run validate:live
689
+ npm run validate:swarm
690
+ ```
691
+
692
+ The live validator starts a planner, worker, reviewer, and operator sidecar,
693
+ waits for full peer discovery, exercises plan/task/review/approve/respond/event
694
+ flows, and fails if the room does not converge.
695
+
696
+ The swarm validator connects two real peers, transfers a 2 MiB file as 2,048
697
+ chunks, verifies the paged manifest exchange, and compares the final sha256.
698
+ Set `NINJA_P2P_TEST_PACKAGE_ROOT` to a clean installed package directory to
699
+ exercise the exact tarball and its selected SDK version instead of this checkout.
700
+
701
+ ## CLI
702
+
703
+ Prove the transport works, then diagnose if it does not:
704
+
705
+ ```bash
706
+ ninja-p2p demo
707
+ ninja-p2p demo --keep
708
+ ninja-p2p doctor
709
+ ```
710
+
711
+ Interactive room session:
712
+
713
+ ```bash
714
+ ninja-p2p connect --room my-room --name Claude --id claude
715
+ ```
716
+
717
+ One-shot room message:
718
+
719
+ ```bash
720
+ ninja-p2p chat --room my-room --name Steve --id steve "hello"
721
+ ```
722
+
723
+ One-shot direct message:
724
+
725
+ ```bash
726
+ ninja-p2p dm --room my-room --name Steve --id steve claude "hello"
727
+ ```
728
+
729
+ One-shot command:
730
+
731
+ ```bash
732
+ ninja-p2p command --room my-room --name Steve --id steve claude status
733
+ ```
734
+
735
+ Minimal persistent sidecar:
736
+
737
+ ```bash
738
+ ninja-p2p start --id codex
739
+ ```
740
+
741
+ Persistent sidecar with optional discovery metadata:
742
+
743
+ ```bash
744
+ ninja-p2p start --room ai-room --name Codex --id codex --runtime codex-cli --provider openai --model gpt-5 --can review,tests
745
+ ```
746
+
747
+ Sidecar status:
748
+
749
+ ```bash
750
+ ninja-p2p status --id codex
751
+ ```
752
+
753
+ Inbox summary:
754
+
755
+ ```bash
756
+ ninja-p2p notify --id codex
757
+ ```
758
+
759
+ Read pending messages:
760
+
761
+ ```bash
762
+ ninja-p2p read --id codex --take 10
763
+ ```
764
+
765
+ Block until messages arrive:
766
+
767
+ ```bash
768
+ ninja-p2p wait --id codex
769
+ ninja-p2p wait --id codex --timeout 60000
770
+ ```
771
+
772
+ Run a command automatically when messages arrive:
773
+
774
+ ```bash
775
+ ninja-p2p start --id codex --on-message "codex exec 'Check your ninja-p2p inbox'"
776
+ ```
777
+
778
+ Queue a direct message through the running sidecar:
779
+
780
+ ```bash
781
+ ninja-p2p dm --id codex human "working on it"
782
+ ```
783
+
784
+ Queue a file or image through the running sidecar:
785
+
786
+ ```bash
787
+ ninja-p2p send-file --id codex reviewer ./notes.txt
788
+ ninja-p2p send-image --id codex reviewer ./diagram.png
789
+ ```
790
+
791
+ List and pull from a shared folder:
792
+
793
+ ```bash
794
+ ninja-p2p shares --id codex worker
795
+ ninja-p2p list-files --id codex worker docs
796
+ ninja-p2p get-file --id codex worker docs guide.md
797
+ ```
798
+
799
+ Ask another sidecar what it can do:
800
+
801
+ ```bash
802
+ ninja-p2p command --id codex claude capabilities
803
+ ```
804
+
805
+ Ask for a plan, review, or approval:
806
+
807
+ ```bash
808
+ ninja-p2p plan --id codex planner "Suggest a safe rollout"
809
+ ninja-p2p review --id codex reviewer "Review PR #42"
810
+ ninja-p2p approve --id codex reviewer "Approve this plan"
811
+ ```
812
+
813
+ Reply to a request with a structured result:
814
+
815
+ ```bash
816
+ ninja-p2p respond --id codex planner <requestId> '{"approved":true}'
817
+ ```
818
+
819
+ Stop the sidecar:
820
+
821
+ ```bash
822
+ ninja-p2p stop --id codex
823
+ ```
824
+
825
+ Install the optional skills:
826
+
827
+ ```bash
828
+ ninja-p2p install-skill codex
829
+ ninja-p2p install-skill claude
830
+ ```
831
+
832
+ Useful env vars:
833
+
834
+ - `NINJA_ROOM`
835
+ - `NINJA_NAME`
836
+ - `NINJA_ID`
837
+ - `NINJA_ROLE`
838
+ - `NINJA_PASSWORD`
839
+ - `NINJA_STATE_DIR`
840
+
841
+ ## Install As A Library
842
+
843
+ ```bash
844
+ npm install @vdoninja/ninja-p2p @roamhq/wrtc
845
+ ```
846
+
847
+ Notes:
848
+
849
+ - `@vdoninja/sdk` 1.4.1 or newer is installed automatically
850
+ - `ws` is installed directly for Node 20 and Social Stream compatibility
851
+ - `@roamhq/wrtc` is recommended for Node bots that need WebRTC support
852
+
853
+ ## Library Quick Start
854
+
855
+ ```ts
856
+ import { VDOBridge } from "@vdoninja/ninja-p2p";
857
+
858
+ const bridge = new VDOBridge({
859
+ room: "agents_room",
860
+ streamId: "planner_bot",
861
+ identity: {
862
+ streamId: "planner_bot",
863
+ role: "agent",
864
+ name: "Planner",
865
+ },
866
+ password: false,
867
+ skills: ["chat", "search"],
868
+ topics: ["events"],
869
+ });
870
+
871
+ await bridge.connect();
872
+
873
+ bridge.chat("Planner online");
874
+ bridge.chat("sync now", "worker_bot");
875
+ bridge.publishEvent("events", "status_change", { status: "busy" });
876
+
877
+ bridge.bus.on("message:chat", (envelope) => {
878
+ console.log(`${envelope.from.name}: ${envelope.payload.text}`);
879
+ });
880
+ ```
881
+
882
+ ## Human Operator Example
883
+
884
+ One simple pattern is to put a human-operated process in the same room as the bots.
885
+
886
+ Agent:
887
+
888
+ ```ts
889
+ import { VDOBridge } from "@vdoninja/ninja-p2p";
890
+
891
+ const worker = new VDOBridge({
892
+ room: "agents_room",
893
+ streamId: "worker_bot",
894
+ identity: {
895
+ streamId: "worker_bot",
896
+ role: "agent",
897
+ name: "Worker",
898
+ },
899
+ password: false,
900
+ skills: ["status", "say"],
901
+ });
902
+
903
+ await worker.connect();
904
+
905
+ worker.bus.on("message:command", (envelope) => {
906
+ const payload = envelope.payload as { command?: string; args?: { text?: string } };
907
+
908
+ if (payload.command === "status") {
909
+ worker.commandResponse(envelope, {
910
+ status: "idle",
911
+ peers: worker.peers.toJSON(),
912
+ });
913
+ return;
914
+ }
915
+
916
+ if (payload.command === "say") {
917
+ console.log(payload.args?.text ?? "");
918
+ worker.commandResponse(envelope, { ok: true });
919
+ return;
920
+ }
921
+
922
+ worker.commandResponse(envelope, undefined, `unknown command: ${payload.command ?? "?"}`);
923
+ });
924
+ ```
925
+
926
+ Operator:
927
+
928
+ ```ts
929
+ import { VDOBridge } from "@vdoninja/ninja-p2p";
930
+
931
+ const operator = new VDOBridge({
932
+ room: "agents_room",
933
+ streamId: "steve_operator",
934
+ identity: {
935
+ streamId: "steve_operator",
936
+ role: "operator",
937
+ name: "Steve",
938
+ },
939
+ password: false,
940
+ });
941
+
942
+ await operator.connect();
943
+
944
+ operator.command("worker_bot", "status");
945
+ operator.command("worker_bot", "say", { text: "hello from the operator" });
946
+
947
+ operator.bus.on("message:command_response", (envelope) => {
948
+ console.log(envelope.payload);
949
+ });
950
+ ```
951
+
952
+ The browser dashboard can also join the same room:
953
+
954
+ ```text
955
+ dashboard.html?room=agents_room&password=false&name=Steve&autoconnect=true
956
+ ```
957
+
958
+ The same UI is hosted, so you do not have to open a local file:
959
+
960
+ ```text
961
+ https://steveseguin.github.io/ninja-p2p/dashboard.html?room=agents_room&password=false&name=Steve&autoconnect=true
962
+ ```
963
+
964
+ The project landing page lives at the root of that site, and `docs/dashboard.html` is the copy it serves. Run `npm run sync:docs` after editing `dashboard.html` to update it.
965
+
966
+ That browser UI can:
967
+
968
+ - enter a room and optional password
969
+ - see connected bots and operators
970
+ - select a peer and DM it directly
971
+ - broadcast to the whole room
972
+ - inspect the selected peer's announced profile, capabilities, asks, and shared folders
973
+ - browse a selected peer's declared shared folders and request one file at a time
974
+ - send a local file to the selected peer, which lands in that peer's downloads folder
975
+ - download files that arrive over the room connection
976
+ - send slash-style commands like `/profile`, `/capabilities`, `/inbox`, `/status`, `/history`, `/peers`, `/shares`, `/ls <peer> <share> [path]`, `/get <peer> <share> <path>`, and `/cmd <peer> <command> [json]`
977
+ - send operator-friendly shortcuts like `/plan`, `/review`, `/approve`, and `/respond`
978
+
979
+ One honest caveat: GitHub Pages is just a static host. It can join a known room, but it will not list all rooms for you or store durable history on its own. Also, if the room password matters, entering it into the page is better than putting it in the URL. The dashboard can browse shares and use the simple transfer protocol, but it does not implement swarm or a full sync UI. Browser uploads are capped at 256 MiB and browser downloads at 64 MiB because the page holds them in memory.
980
+
981
+ ## Coordination Helpers
982
+
983
+ - `bridge.chat(text, to?)`
984
+ - `bridge.chatTopic(topic, text)`
985
+ - `bridge.command(targetStreamId, command, args?)`
986
+ - `bridge.commandResponse(message, result?, error?)`
987
+ - `bridge.publishEvent(topic, kind, data?)`
988
+ - `bridge.reply(message, type, payload)`
989
+ - `bridge.ack(message, payload?)`
990
+ - `bridge.requestHistory(targetStreamId, count?)`
991
+
992
+ These are lightweight coordination messages. They are useful, but they are not hard delivery guarantees.
993
+
994
+ ## Raw Data, Media, And Advanced SDK Access
995
+
996
+ This package focuses on data-channel messaging.
997
+
998
+ The underlying VDO.Ninja SDK can also:
999
+
1000
+ - publish and view audio or video tracks
1001
+ - emit `track` events
1002
+ - send binary payloads over the data channel
1003
+
1004
+ This wrapper exposes the control lane plus the optional binary lane:
1005
+
1006
+ - `bridge.sendRaw(data, targetStreamId?)` sends JSON-compatible SDK data on the
1007
+ control lane
1008
+ - `bridge.supportsBinary()` reports whether the installed SDK has the 1.4.1
1009
+ binary API
1010
+ - `await bridge.sendBinaryTo(targetStreamId, bytes)` sends a `Uint8Array`
1011
+ without JSON or base64
1012
+ - `bridge.on("binary", ({ streamId, bytes }) => ...)` receives those bytes
1013
+ - `bridge.bufferedBytesFor(targetStreamId)` and
1014
+ `bridge.maxMessageSizeFor(targetStreamId)` expose binary-lane backpressure and
1015
+ the negotiated SCTP limit
1016
+ - `bridge.getSDK()`
1017
+
1018
+ Example:
1019
+
1020
+ ```ts
1021
+ bridge.on("binary", ({ streamId, bytes }) => {
1022
+ console.log("binary frame", streamId, bytes.byteLength);
1023
+ });
1024
+
1025
+ const bytes = new Uint8Array([1, 2, 3]);
1026
+ if (bridge.supportsBinary()) {
1027
+ await bridge.sendBinaryTo("worker_bot", bytes);
1028
+ }
1029
+ ```
1030
+
1031
+ `sendRaw()` is not the binary API: SDK 1.4.0 JSON-stringifies non-string values,
1032
+ and SDK 1.4.1 reserves `sendBinary()` for raw bytes. The built-in swarm handles
1033
+ that compatibility choice per peer automatically. A custom protocol must
1034
+ negotiate receiver support itself; a successful local send does not prove that
1035
+ an older remote peer understands the bytes.
1036
+
1037
+ The CLI already provides simple file/image transfer and the resumable swarm.
1038
+ Use the lower-level SDK or binary methods only when you need a different framing
1039
+ protocol or media behavior.
1040
+
1041
+ ## Files
1042
+
1043
+ - `src/vdo-bridge.ts`: connection lifecycle and SDK integration
1044
+ - `src/message-bus.ts`: chat, direct messages, topics, history, offline queue
1045
+ - `src/peer-registry.ts`: peer state and presence
1046
+ - `src/protocol.ts`: message envelope format
1047
+ - `src/agent-state.ts`: local inbox, outbox, and sidecar state
1048
+ - `src/wake.ts`: wake-on-message hooks, batching, and rate limiting
1049
+ - `src/demo.ts`: the one-command live round-trip self-test
1050
+ - `src/doctor.ts`: environment and connectivity diagnostics
1051
+ - `src/social-stream.ts`: the Social Stream Ninja live-chat bridge
1052
+ - `src/swarm.ts`: chunk bitfields, piece selection, and sparse chunk storage
1053
+ - `src/swarm-session.ts`: per-file transfer state, verification, and peer scoring
1054
+ - `src/swarm-manager.ts`: binds swarm sessions to a live room
1055
+ - `dashboard.html`: browser monitor and chat client
1056
+ - `.codex/skills/ninja-p2p`: optional Codex skill
1057
+ - `.agents/skills/ninja-p2p`: Codex compatibility copy for older layouts
1058
+ - `.claude/skills/ninja-p2p`: optional Claude Code skill
1059
+
1060
+ ## Tests
1061
+
1062
+ ```bash
1063
+ npm test
1064
+ npm run build
1065
+ npm run validate:live
1066
+ npm run validate:swarm
1067
+ ```
1068
+
1069
+ ## Support
1070
+
1071
+ - Discord: https://discord.vdo.ninja
1072
+ - VDO.Ninja: https://vdo.ninja
1073
+ - Social Stream Ninja: https://socialstream.ninja
1074
+
1075
+ ## License
1076
+
1077
+ MIT