@vdoninja/ninja-p2p 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -93
- package/dashboard.html +1 -1
- package/dist/agent-state.js +8 -1
- package/dist/message-bus.d.ts +2 -1
- package/dist/message-bus.js +23 -13
- package/dist/vdo-bridge.js +8 -8
- package/docs/images/agent-room-dashboard.png +0 -0
- package/docs/protocol-and-reliability.md +38 -0
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -1,134 +1,84 @@
|
|
|
1
1
|
# ninja-p2p
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## TL;DR
|
|
4
4
|
|
|
5
|
-
|
|
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
6
|
|
|
7
|
-
|
|
8
|
-
|
|
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)
|
|
9
14
|
|
|
10
15
|
<p align="center">
|
|
11
|
-
<img src="docs/images/
|
|
12
|
-
<img src="docs/images/readme-demo-codex.png" alt="Codex using ninja-p2p to send and read messages in the same room" width="32%">
|
|
13
|
-
<img src="docs/images/readme-demo-dashboard.png" alt="The browser dashboard observing the same ninja-p2p room" width="32%">
|
|
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>
|
|
14
17
|
</p>
|
|
15
18
|
|
|
16
|
-
<p align="center"><em>
|
|
19
|
+
<p align="center"><em>A real room: two agent sidecars talking while a human watches from the browser dashboard.</em></p>
|
|
17
20
|
|
|
18
|
-
##
|
|
21
|
+
## The Simple Mental Model
|
|
19
22
|
|
|
20
|
-
|
|
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.
|
|
21
27
|
|
|
22
|
-
|
|
28
|
+
## Start Two Agents
|
|
23
29
|
|
|
24
|
-
|
|
30
|
+
Install once:
|
|
25
31
|
|
|
26
32
|
```bash
|
|
27
33
|
npm install -g @vdoninja/ninja-p2p @roamhq/wrtc
|
|
28
34
|
```
|
|
29
35
|
|
|
30
|
-
|
|
36
|
+
Start the first agent. A private room name is generated automatically:
|
|
31
37
|
|
|
32
38
|
```bash
|
|
33
|
-
ninja-p2p
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
3. In Claude Code, start the sidecar:
|
|
37
|
-
|
|
38
|
-
```text
|
|
39
|
-
/ninja-p2p start
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
That starts the background sidecar for `claude`. If you do not pass `--room`, `ninja-p2p` generates one automatically.
|
|
43
|
-
|
|
44
|
-
4. Ask Claude which room it is in:
|
|
45
|
-
|
|
46
|
-
```text
|
|
47
|
-
/ninja-p2p room
|
|
39
|
+
ninja-p2p start --id codex
|
|
40
|
+
ninja-p2p room --id codex
|
|
48
41
|
```
|
|
49
42
|
|
|
50
|
-
|
|
43
|
+
Use the room name printed above to start the second agent:
|
|
51
44
|
|
|
52
45
|
```bash
|
|
53
|
-
ninja-p2p start --room <
|
|
46
|
+
ninja-p2p start --room <room-name> --id claude
|
|
54
47
|
```
|
|
55
48
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
```text
|
|
59
|
-
/ninja-p2p menu
|
|
60
|
-
/ninja-p2p notify
|
|
61
|
-
/ninja-p2p read --take 10
|
|
62
|
-
/ninja-p2p dm codex "Can you review this plan?"
|
|
63
|
-
/ninja-p2p send-file codex ./notes.txt
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
That is the whole model:
|
|
67
|
-
|
|
68
|
-
- `/ninja-p2p start` launches the detached background sidecar
|
|
69
|
-
- `/ninja-p2p room` shows the current room and how another agent joins it
|
|
70
|
-
- Claude uses `/ninja-p2p ...` during its turns
|
|
71
|
-
- `notify` tells Claude whether anything is waiting
|
|
72
|
-
- `read` pulls pending messages into the current turn
|
|
73
|
-
|
|
74
|
-
Restart Claude Code after installing the skill if it does not appear immediately.
|
|
75
|
-
|
|
76
|
-
## Quick Start For Codex CLI
|
|
77
|
-
|
|
78
|
-
Do not use `connect` for Codex CLI either. Use a persistent sidecar.
|
|
79
|
-
|
|
80
|
-
This is the actual Codex CLI setup:
|
|
81
|
-
|
|
82
|
-
1. Install the CLI:
|
|
49
|
+
Now they can talk:
|
|
83
50
|
|
|
84
51
|
```bash
|
|
85
|
-
|
|
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"
|
|
86
56
|
```
|
|
87
57
|
|
|
88
|
-
|
|
58
|
+
That is the core product. Profiles, commands, approvals, file transfer, shared folders, and the dashboard build on the same room and inbox.
|
|
89
59
|
|
|
90
|
-
|
|
91
|
-
ninja-p2p install-skill codex
|
|
92
|
-
```
|
|
60
|
+
## Make It Feel Native In Codex Or Claude
|
|
93
61
|
|
|
94
|
-
|
|
62
|
+
The optional skill teaches the agent when and how to use the CLI:
|
|
95
63
|
|
|
96
64
|
```bash
|
|
97
|
-
ninja-p2p
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
That starts the background sidecar for `codex`. If you do not pass `--room`, `ninja-p2p` generates one automatically.
|
|
101
|
-
|
|
102
|
-
4. Ask Codex which room it is in:
|
|
103
|
-
|
|
104
|
-
```bash
|
|
105
|
-
ninja-p2p room --id codex
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
5. Start the second agent in that same room:
|
|
109
|
-
|
|
110
|
-
```text
|
|
111
|
-
/ninja-p2p start --room <that-room>
|
|
65
|
+
ninja-p2p install-skill codex
|
|
66
|
+
ninja-p2p install-skill claude
|
|
112
67
|
```
|
|
113
68
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
ninja-p2p menu --id codex
|
|
118
|
-
ninja-p2p room --id codex
|
|
119
|
-
ninja-p2p notify --id codex
|
|
120
|
-
ninja-p2p read --id codex --take 10
|
|
121
|
-
ninja-p2p dm --id codex claude "I pushed the patch"
|
|
122
|
-
ninja-p2p shares --id codex claude
|
|
123
|
-
```
|
|
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.
|
|
124
72
|
|
|
125
|
-
|
|
73
|
+
## Which VDO.Ninja Package Do I Need?
|
|
126
74
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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) |
|
|
130
80
|
|
|
131
|
-
|
|
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.
|
|
132
82
|
|
|
133
83
|
## How Joining A Room Works
|
|
134
84
|
|
package/dashboard.html
CHANGED
|
@@ -162,7 +162,7 @@ input:focus,select:focus{border-color:var(--accent)}
|
|
|
162
162
|
let connected = false;
|
|
163
163
|
let myStreamId = '';
|
|
164
164
|
let myIdentity = null;
|
|
165
|
-
const DASHBOARD_VERSION = '0.1.
|
|
165
|
+
const DASHBOARD_VERSION = '0.1.4';
|
|
166
166
|
const peers = new Map(); // streamId -> {uuid, identity, skills, status, connected}
|
|
167
167
|
const peerUuidToStream = new Map();
|
|
168
168
|
const messages = [];
|
package/dist/agent-state.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { createMessageId, } from "./protocol.js";
|
|
4
4
|
const SESSION_FILE = "session.json";
|
|
5
5
|
const PEERS_FILE = "peers.json";
|
|
6
|
+
let lastQueuedActionAt = 0;
|
|
6
7
|
export function ensureAgentState(stateDir) {
|
|
7
8
|
const resolved = path.resolve(stateDir);
|
|
8
9
|
const paths = {
|
|
@@ -40,7 +41,9 @@ export function queueAgentAction(stateDir, action) {
|
|
|
40
41
|
const queued = {
|
|
41
42
|
...action,
|
|
42
43
|
id: createMessageId(),
|
|
43
|
-
|
|
44
|
+
// Date.now() can repeat during a burst. Keep timestamps monotonic so the
|
|
45
|
+
// filesystem outbox remains FIFO even when several actions share a tick.
|
|
46
|
+
createdAt: nextQueuedActionTimestamp(),
|
|
44
47
|
};
|
|
45
48
|
const filename = `${queued.createdAt}_${queued.id}.json`;
|
|
46
49
|
writeJsonAtomic(path.join(paths.outboxDir, filename), queued);
|
|
@@ -173,6 +176,10 @@ function listJsonFiles(dir) {
|
|
|
173
176
|
.map((name) => path.join(dir, name))
|
|
174
177
|
.sort();
|
|
175
178
|
}
|
|
179
|
+
function nextQueuedActionTimestamp() {
|
|
180
|
+
lastQueuedActionAt = Math.max(Date.now(), lastQueuedActionAt + 1);
|
|
181
|
+
return lastQueuedActionAt;
|
|
182
|
+
}
|
|
176
183
|
function readJsonFile(filePath) {
|
|
177
184
|
try {
|
|
178
185
|
const raw = readFileSync(filePath, "utf8");
|
package/dist/message-bus.d.ts
CHANGED
|
@@ -16,7 +16,8 @@ export type KeywordTrigger = {
|
|
|
16
16
|
pattern: RegExp;
|
|
17
17
|
handler: (msg: MessageEnvelope) => void;
|
|
18
18
|
};
|
|
19
|
-
|
|
19
|
+
/** Return false when the transport could not accept the message yet. */
|
|
20
|
+
export type SendDataFn = (data: object, target?: unknown) => boolean | void;
|
|
20
21
|
export type MessageBusOptions = {
|
|
21
22
|
/** Max messages kept in history ring buffer. Default: 200. */
|
|
22
23
|
historySize?: number;
|
package/dist/message-bus.js
CHANGED
|
@@ -69,10 +69,7 @@ export class MessageBus extends EventEmitter {
|
|
|
69
69
|
send(targetStreamId, type, payload) {
|
|
70
70
|
const envelope = createEnvelope(this.identity, type, payload, { to: targetStreamId });
|
|
71
71
|
this.addToHistory(envelope);
|
|
72
|
-
if (this.peers.isConnected(targetStreamId)) {
|
|
73
|
-
this.rawSend(envelope, targetStreamId);
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
72
|
+
if (!this.peers.isConnected(targetStreamId) || !this.rawSend(envelope, targetStreamId)) {
|
|
76
73
|
this.enqueueOffline(targetStreamId, envelope);
|
|
77
74
|
}
|
|
78
75
|
return envelope;
|
|
@@ -142,13 +139,21 @@ export class MessageBus extends EventEmitter {
|
|
|
142
139
|
const queue = this.offlineQueues.get(streamId);
|
|
143
140
|
if (!queue || queue.length === 0)
|
|
144
141
|
return [];
|
|
145
|
-
|
|
146
|
-
// Send each queued message wrapped as history_replay
|
|
142
|
+
const flushed = [];
|
|
147
143
|
for (const msg of queue) {
|
|
148
|
-
|
|
149
|
-
|
|
144
|
+
// Preserve the original type and ID. Wrapping queued commands as
|
|
145
|
+
// history_replay prevented normal command/file handlers from seeing them.
|
|
146
|
+
if (!this.rawSend(msg, streamId))
|
|
147
|
+
break;
|
|
148
|
+
flushed.push(msg);
|
|
149
|
+
}
|
|
150
|
+
if (flushed.length === queue.length) {
|
|
151
|
+
this.offlineQueues.delete(streamId);
|
|
150
152
|
}
|
|
151
|
-
|
|
153
|
+
else if (flushed.length > 0) {
|
|
154
|
+
this.offlineQueues.set(streamId, queue.slice(flushed.length));
|
|
155
|
+
}
|
|
156
|
+
return flushed;
|
|
152
157
|
}
|
|
153
158
|
/** Get the number of queued messages for a peer. */
|
|
154
159
|
getOfflineQueueSize(streamId) {
|
|
@@ -174,23 +179,27 @@ export class MessageBus extends EventEmitter {
|
|
|
174
179
|
// ── Internals ────────────────────────────────────────────────────────────
|
|
175
180
|
rawSend(envelope, target) {
|
|
176
181
|
if (!this.sendDataFn)
|
|
177
|
-
return;
|
|
182
|
+
return false;
|
|
178
183
|
const wire = envelopeToWire(envelope);
|
|
184
|
+
let accepted;
|
|
179
185
|
if (target) {
|
|
180
186
|
// Prefer uuid targeting once we know it; streamID targeting can be ambiguous
|
|
181
187
|
// before announce/rekey completes on some SDK connection paths.
|
|
182
188
|
const peer = this.peers.getPeer(target);
|
|
183
189
|
if (peer?.uuid) {
|
|
184
|
-
this.sendDataFn(wire, { uuid: peer.uuid });
|
|
190
|
+
accepted = this.sendDataFn(wire, { uuid: peer.uuid });
|
|
185
191
|
}
|
|
186
192
|
else {
|
|
187
|
-
this.sendDataFn(wire, { streamID: target });
|
|
193
|
+
accepted = this.sendDataFn(wire, { streamID: target });
|
|
188
194
|
}
|
|
189
195
|
}
|
|
190
196
|
else {
|
|
191
197
|
// Broadcast to all
|
|
192
|
-
this.sendDataFn(wire);
|
|
198
|
+
accepted = this.sendDataFn(wire);
|
|
193
199
|
}
|
|
200
|
+
// Void preserves compatibility with existing transports written before
|
|
201
|
+
// acceptance results were supported. Only an explicit false is a failure.
|
|
202
|
+
return accepted !== false;
|
|
194
203
|
}
|
|
195
204
|
addToHistory(envelope) {
|
|
196
205
|
// Don't store heartbeat or file chunk traffic in history.
|
|
@@ -221,6 +230,7 @@ export class MessageBus extends EventEmitter {
|
|
|
221
230
|
if (!text)
|
|
222
231
|
return;
|
|
223
232
|
for (const trigger of this.triggers) {
|
|
233
|
+
trigger.pattern.lastIndex = 0;
|
|
224
234
|
if (trigger.pattern.test(text)) {
|
|
225
235
|
try {
|
|
226
236
|
trigger.handler(envelope);
|
package/dist/vdo-bridge.js
CHANGED
|
@@ -24,7 +24,7 @@ export class VDOBridge extends EventEmitter {
|
|
|
24
24
|
skills;
|
|
25
25
|
status = "idle";
|
|
26
26
|
statusDetail = "";
|
|
27
|
-
version = "0.1.
|
|
27
|
+
version = "0.1.4";
|
|
28
28
|
agentProfile;
|
|
29
29
|
viewedStreamIds = new Set();
|
|
30
30
|
constructor(options) {
|
|
@@ -62,12 +62,13 @@ export class VDOBridge extends EventEmitter {
|
|
|
62
62
|
// Set the send function on the bus
|
|
63
63
|
this.bus.setSendDataFn((data, target) => {
|
|
64
64
|
if (!this.sdk)
|
|
65
|
-
return;
|
|
65
|
+
return false;
|
|
66
66
|
try {
|
|
67
|
-
this.sdk.sendData(data, target ?? undefined);
|
|
67
|
+
return this.sdk.sendData(data, target ?? undefined) !== false;
|
|
68
68
|
}
|
|
69
69
|
catch (err) {
|
|
70
70
|
this.emitBridgeError(err);
|
|
71
|
+
return false;
|
|
71
72
|
}
|
|
72
73
|
});
|
|
73
74
|
await this.sdk.connect();
|
|
@@ -181,17 +182,15 @@ export class VDOBridge extends EventEmitter {
|
|
|
181
182
|
return false;
|
|
182
183
|
try {
|
|
183
184
|
if (!targetStreamId) {
|
|
184
|
-
this.sdk.sendData(data, { allowFallback: true });
|
|
185
|
-
return true;
|
|
185
|
+
return this.sdk.sendData(data, { allowFallback: true }) !== false;
|
|
186
186
|
}
|
|
187
187
|
const peer = this.peers.getPeer(targetStreamId);
|
|
188
188
|
if (peer?.uuid) {
|
|
189
|
-
this.sdk.sendData(data, {
|
|
189
|
+
return this.sdk.sendData(data, { uuid: peer.uuid, allowFallback: true }) !== false;
|
|
190
190
|
}
|
|
191
191
|
else {
|
|
192
|
-
this.sdk.sendData(data, { streamID: targetStreamId, allowFallback: true });
|
|
192
|
+
return this.sdk.sendData(data, { streamID: targetStreamId, allowFallback: true }) !== false;
|
|
193
193
|
}
|
|
194
|
-
return true;
|
|
195
194
|
}
|
|
196
195
|
catch (err) {
|
|
197
196
|
this.emitBridgeError(err);
|
|
@@ -288,6 +287,7 @@ export class VDOBridge extends EventEmitter {
|
|
|
288
287
|
switch (envelope.type) {
|
|
289
288
|
case "announce":
|
|
290
289
|
this.peers.updateFromAnnounce(senderStreamId, envelope.from, envelope.payload);
|
|
290
|
+
this.bus.flushOfflineQueue(senderStreamId);
|
|
291
291
|
this.emit("peer:announce", { streamId: senderStreamId, identity: envelope.from, announce: envelope.payload });
|
|
292
292
|
break;
|
|
293
293
|
case "skill_update":
|
|
Binary file
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Protocol and reliability
|
|
2
|
+
|
|
3
|
+
`ninja-p2p` is an agent coordination layer over VDO.Ninja WebRTC data channels. It does not replace or extend VDO.Ninja signaling.
|
|
4
|
+
|
|
5
|
+
## Layers
|
|
6
|
+
|
|
7
|
+
1. The VDO.Ninja SDK joins a room, discovers published stream IDs, and establishes WebRTC peer connections and data channels.
|
|
8
|
+
2. `VDOBridge` asks the SDK to view discovered peers as data-only connections.
|
|
9
|
+
3. `MessageBus` sends a versioned JSON envelope through those channels.
|
|
10
|
+
4. The optional sidecar stores inbox and outbox records on the local machine so a turn-based agent can read them later.
|
|
11
|
+
|
|
12
|
+
The application envelope contains an ID, timestamp, sender identity, message type, optional target/topic, and payload. It is data carried through VDO.Ninja, not a new WebSocket command.
|
|
13
|
+
|
|
14
|
+
## Delivery semantics
|
|
15
|
+
|
|
16
|
+
- Direct messages are sent when the SDK accepts them for an open data channel.
|
|
17
|
+
- If a known peer is offline, or the channel exists but is not ready yet, the message is queued in memory.
|
|
18
|
+
- A queue flush retries the original envelope, including its message ID and type, and removes only messages the SDK accepted. A failed flush leaves the remaining messages in order for the next channel-open or announce event.
|
|
19
|
+
- The persistent CLI sidecar additionally stores actions on disk before its live process sends them.
|
|
20
|
+
- There is no durable remote broker and no exactly-once guarantee. Applications that require confirmation should use message IDs with `ack` or `command_response` and make handlers idempotent.
|
|
21
|
+
- Room broadcasts are best-effort. They are not retained independently for every absent peer.
|
|
22
|
+
|
|
23
|
+
## Connection race this prevents
|
|
24
|
+
|
|
25
|
+
WebRTC emits several milestones. `peerConnected` can occur before `dataChannelOpen`. Older code treated the first event as send-ready, so an agent replying in that interval could receive a normal-looking local envelope even though the SDK rejected the send. The bridge now observes the SDK's boolean send result and queues an explicitly rejected direct message.
|
|
26
|
+
|
|
27
|
+
A second race occurred during reconnect: the queue was deleted before replay attempts. If the data channel closed between the open event and the first replay, every queued message was lost locally. Queue entries now remain until each replay is accepted.
|
|
28
|
+
|
|
29
|
+
## Compatibility
|
|
30
|
+
|
|
31
|
+
- The wire envelope format is unchanged.
|
|
32
|
+
- Existing send callbacks that return `void` remain accepted; only an explicit `false` means the transport rejected a send.
|
|
33
|
+
- SDK targets use the SDK's documented `{ uuid }` or `{ streamID }` forms.
|
|
34
|
+
- Existing VDO.Ninja room and signaling behavior remains authoritative.
|
|
35
|
+
|
|
36
|
+
## Security boundary
|
|
37
|
+
|
|
38
|
+
WebRTC encrypts data in transit. Room names and optional VDO.Ninja passwords control discovery/connection behavior, but they are not an application authorization system. Validate commands at the receiving agent, expose only intentional shared folders, and do not treat peer-supplied names or capabilities as trusted identity claims.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vdoninja/ninja-p2p",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Open source P2P agent messaging and coordination over VDO.Ninja WebRTC data channels",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -48,6 +48,8 @@
|
|
|
48
48
|
"dist",
|
|
49
49
|
"dashboard.html",
|
|
50
50
|
"README.md",
|
|
51
|
+
"docs/images/agent-room-dashboard.png",
|
|
52
|
+
"docs/protocol-and-reliability.md",
|
|
51
53
|
".codex/skills/ninja-p2p",
|
|
52
54
|
".agents/skills/ninja-p2p",
|
|
53
55
|
".claude/skills/ninja-p2p"
|
|
@@ -57,6 +59,7 @@
|
|
|
57
59
|
"test": "tsx --test tests/**/*.test.ts",
|
|
58
60
|
"prebuild": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
59
61
|
"build": "tsc -p tsconfig.json",
|
|
62
|
+
"check": "npm test && npm run build",
|
|
60
63
|
"validate:live": "node ./scripts/validate-live.mjs",
|
|
61
64
|
"sync:docs": "node -e \"const fs=require('node:fs'); fs.mkdirSync('docs',{recursive:true}); fs.copyFileSync('dashboard.html','docs/index.html');\"",
|
|
62
65
|
"prepare": "npm run build"
|
|
@@ -95,7 +98,7 @@
|
|
|
95
98
|
},
|
|
96
99
|
"homepage": "https://github.com/steveseguin/ninja-p2p#readme",
|
|
97
100
|
"dependencies": {
|
|
98
|
-
"@vdoninja/sdk": "^1.
|
|
101
|
+
"@vdoninja/sdk": "^1.4.0"
|
|
99
102
|
},
|
|
100
103
|
"peerDependencies": {
|
|
101
104
|
"@roamhq/wrtc": "^0.8.0"
|