@cleocode/cleo-os 2026.4.13 → 2026.4.17

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.
@@ -0,0 +1,344 @@
1
+ /**
2
+ * CleoOS chat room — inter-agent messaging TUI.
3
+ *
4
+ * Installed to: $CLEO_HOME/pi-extensions/cleo-chatroom.ts
5
+ * Loaded by: Pi via `-e <path>` or settings.json extensions array
6
+ *
7
+ * Wave 7 — surfaces inter-agent traffic as a TUI panel per ULTRAPLAN
8
+ * section 13. Agents communicate through four tools:
9
+ *
10
+ * - `send_to_lead` — worker sends a message to their lead.
11
+ * - `broadcast_to_team` — lead broadcasts to all group workers.
12
+ * - `report_to_orchestrator` — lead reports status to the orchestrator.
13
+ * - `query_peer` — worker queries another worker in the same group.
14
+ *
15
+ * Each tool appends a structured JSONL entry to the Pi session's message
16
+ * log. A TUI widget (registered on `session_start`) renders the last N
17
+ * messages in a scrollable panel below the editor.
18
+ *
19
+ * This is a TEMPLATE extension — it uses `import type` for Pi types and
20
+ * mirrors the patterns established by the existing extensions in
21
+ * `packages/cleo/templates/cleoos-hub/pi-extensions/`.
22
+ *
23
+ * @packageDocumentation
24
+ */
25
+ import { appendFileSync, mkdirSync } from "node:fs";
26
+ import { join } from "node:path";
27
+ import { Type } from "@sinclair/typebox";
28
+ // ---------------------------------------------------------------------------
29
+ // Module state
30
+ // ---------------------------------------------------------------------------
31
+ const WIDGET_KEY = "cleo-chatroom";
32
+ const STATUS_KEY = "cleo-chatroom";
33
+ const MAX_DISPLAY_MESSAGES = 15;
34
+ /** In-memory message buffer for the current session. */
35
+ const messages = [];
36
+ /** Path to the JSONL log file (set on session_start). */
37
+ let logPath = null;
38
+ // ---------------------------------------------------------------------------
39
+ // Helpers
40
+ // ---------------------------------------------------------------------------
41
+ /**
42
+ * Record a chat message: push to the in-memory buffer and append to the
43
+ * JSONL log file. The log file is created lazily on first write.
44
+ *
45
+ * @param msg - The chat message to record.
46
+ */
47
+ function recordMessage(msg) {
48
+ messages.push(msg);
49
+ if (logPath) {
50
+ try {
51
+ appendFileSync(logPath, JSON.stringify(msg) + "\n", "utf-8");
52
+ }
53
+ catch {
54
+ // Best-effort: never crash Pi over a log write failure.
55
+ }
56
+ }
57
+ }
58
+ /**
59
+ * Return the single-character tier prefix for a chat message row.
60
+ *
61
+ * - `[O]` orchestrator (green in ANSI-capable terminals)
62
+ * - `[L]` lead (yellow)
63
+ * - `[W]` worker (blue, default)
64
+ *
65
+ * @param role - The sending agent's tier role, or `undefined` to default to worker.
66
+ * @returns The three-character prefix string.
67
+ */
68
+ export function tierPrefix(role) {
69
+ switch (role) {
70
+ case "orchestrator":
71
+ return "[O]";
72
+ case "lead":
73
+ return "[L]";
74
+ default:
75
+ return "[W]";
76
+ }
77
+ }
78
+ /**
79
+ * Format a chat message for TUI display.
80
+ *
81
+ * Each row is prefixed with a tier indicator ([O]/[L]/[W]) so orchestrator,
82
+ * lead, and worker traffic is visually distinct in the chat panel (ULTRAPLAN §13).
83
+ *
84
+ * @param msg - The message to format.
85
+ * @returns A single-line string representation.
86
+ */
87
+ export function formatMessage(msg) {
88
+ const time = msg.timestamp.slice(11, 19);
89
+ const prefix = tierPrefix(msg.role);
90
+ return `${prefix} [${time}] ${msg.from} -> ${msg.to}: ${msg.text}`;
91
+ }
92
+ /**
93
+ * Render the chat room widget with the latest messages.
94
+ *
95
+ * @param ctx - The Pi extension context.
96
+ */
97
+ function renderWidget(ctx) {
98
+ if (!ctx.hasUI)
99
+ return;
100
+ const tail = messages.slice(-MAX_DISPLAY_MESSAGES);
101
+ if (tail.length === 0) {
102
+ ctx.ui.setWidget(WIDGET_KEY, ["(no messages yet)"], {
103
+ placement: "belowEditor",
104
+ });
105
+ return;
106
+ }
107
+ const lines = tail.map(formatMessage);
108
+ ctx.ui.setWidget(WIDGET_KEY, lines, { placement: "belowEditor" });
109
+ ctx.ui.setStatus(STATUS_KEY, `Chat: ${messages.length} msg(s)`);
110
+ }
111
+ // ---------------------------------------------------------------------------
112
+ // Tool parameter schemas (TypeBox)
113
+ // ---------------------------------------------------------------------------
114
+ const SendToLeadParams = Type.Object({
115
+ message: Type.String({ description: "Message to send to your team lead" }),
116
+ from: Type.String({ description: "Your agent name" }),
117
+ lead: Type.String({ description: "Name of the lead agent" }),
118
+ role: Type.Optional(Type.Union([
119
+ Type.Literal("orchestrator"),
120
+ Type.Literal("lead"),
121
+ Type.Literal("worker"),
122
+ ], { description: "Tier role of the sending agent for TUI row styling" })),
123
+ });
124
+ const BroadcastToTeamParams = Type.Object({
125
+ message: Type.String({ description: "Message to broadcast to the team" }),
126
+ from: Type.String({ description: "Your agent name (lead)" }),
127
+ group: Type.String({ description: "Team group name (e.g. 'backend')" }),
128
+ role: Type.Optional(Type.Union([
129
+ Type.Literal("orchestrator"),
130
+ Type.Literal("lead"),
131
+ Type.Literal("worker"),
132
+ ], { description: "Tier role of the sending agent for TUI row styling" })),
133
+ });
134
+ const ReportToOrchestratorParams = Type.Object({
135
+ message: Type.String({ description: "Status report for the orchestrator" }),
136
+ from: Type.String({ description: "Your agent name (lead)" }),
137
+ orchestrator: Type.String({
138
+ description: "Name of the orchestrator agent",
139
+ }),
140
+ role: Type.Optional(Type.Union([
141
+ Type.Literal("orchestrator"),
142
+ Type.Literal("lead"),
143
+ Type.Literal("worker"),
144
+ ], { description: "Tier role of the sending agent for TUI row styling" })),
145
+ });
146
+ const QueryPeerParams = Type.Object({
147
+ message: Type.String({ description: "Query for your peer worker" }),
148
+ from: Type.String({ description: "Your agent name" }),
149
+ peer: Type.String({ description: "Name of the peer worker to query" }),
150
+ role: Type.Optional(Type.Union([
151
+ Type.Literal("orchestrator"),
152
+ Type.Literal("lead"),
153
+ Type.Literal("worker"),
154
+ ], { description: "Tier role of the sending agent for TUI row styling" })),
155
+ });
156
+ // ---------------------------------------------------------------------------
157
+ // Pi extension factory
158
+ // ---------------------------------------------------------------------------
159
+ /**
160
+ * Pi extension factory for the CleoOS chat room.
161
+ *
162
+ * Registers four inter-agent communication tools and a TUI widget that
163
+ * displays the message stream. Also registers `/cleo:chat-info` for
164
+ * introspection and clears state on session shutdown.
165
+ *
166
+ * @param pi - The Pi extension API instance.
167
+ */
168
+ export default function (pi) {
169
+ // -------------------------------------------------------------------------
170
+ // session_start: initialize log directory and widget
171
+ // -------------------------------------------------------------------------
172
+ pi.on("session_start", async (_event, ctx) => {
173
+ messages.length = 0;
174
+ logPath = null;
175
+ try {
176
+ const chatDir = join(ctx.cwd, ".cleo", "chat");
177
+ mkdirSync(chatDir, { recursive: true });
178
+ const sessionTs = new Date().toISOString().replace(/[:.]/g, "-");
179
+ logPath = join(chatDir, `chatroom-${sessionTs}.jsonl`);
180
+ }
181
+ catch {
182
+ // Best-effort: widget still works without persistent logging.
183
+ }
184
+ renderWidget(ctx);
185
+ if (ctx.hasUI) {
186
+ ctx.ui.setStatus(STATUS_KEY, "Chat: ready");
187
+ }
188
+ });
189
+ // -------------------------------------------------------------------------
190
+ // Tools: send_to_lead
191
+ // -------------------------------------------------------------------------
192
+ pi.registerTool({
193
+ name: "send_to_lead",
194
+ label: "Send to Lead",
195
+ description: "Send a message from a worker agent to their team lead. " +
196
+ "Used for status updates, questions, and escalations.",
197
+ parameters: SendToLeadParams,
198
+ async execute(_id, params, _signal, _onUpdate, ctx) {
199
+ const msg = {
200
+ timestamp: new Date().toISOString(),
201
+ from: params.from,
202
+ to: params.lead,
203
+ channel: "send_to_lead",
204
+ text: params.message,
205
+ role: params.role,
206
+ };
207
+ recordMessage(msg);
208
+ renderWidget(ctx);
209
+ return {
210
+ content: [
211
+ {
212
+ type: "text",
213
+ text: `Message sent to lead ${params.lead}: ${params.message}`,
214
+ },
215
+ ],
216
+ };
217
+ },
218
+ });
219
+ // -------------------------------------------------------------------------
220
+ // Tools: broadcast_to_team
221
+ // -------------------------------------------------------------------------
222
+ pi.registerTool({
223
+ name: "broadcast_to_team",
224
+ label: "Broadcast to Team",
225
+ description: "Broadcast a message from a lead to all workers in their group. " +
226
+ "Used for coordination directives and status announcements.",
227
+ parameters: BroadcastToTeamParams,
228
+ async execute(_id, params, _signal, _onUpdate, ctx) {
229
+ const msg = {
230
+ timestamp: new Date().toISOString(),
231
+ from: params.from,
232
+ to: `team:${params.group}`,
233
+ channel: "broadcast_to_team",
234
+ text: params.message,
235
+ role: params.role,
236
+ };
237
+ recordMessage(msg);
238
+ renderWidget(ctx);
239
+ return {
240
+ content: [
241
+ {
242
+ type: "text",
243
+ text: `Broadcast to team:${params.group}: ${params.message}`,
244
+ },
245
+ ],
246
+ };
247
+ },
248
+ });
249
+ // -------------------------------------------------------------------------
250
+ // Tools: report_to_orchestrator
251
+ // -------------------------------------------------------------------------
252
+ pi.registerTool({
253
+ name: "report_to_orchestrator",
254
+ label: "Report to Orchestrator",
255
+ description: "Send a status report from a lead to the orchestrator. " +
256
+ "Used for task completion reports, blockers, and escalations.",
257
+ parameters: ReportToOrchestratorParams,
258
+ async execute(_id, params, _signal, _onUpdate, ctx) {
259
+ const msg = {
260
+ timestamp: new Date().toISOString(),
261
+ from: params.from,
262
+ to: params.orchestrator,
263
+ channel: "report_to_orchestrator",
264
+ text: params.message,
265
+ role: params.role,
266
+ };
267
+ recordMessage(msg);
268
+ renderWidget(ctx);
269
+ return {
270
+ content: [
271
+ {
272
+ type: "text",
273
+ text: `Report sent to orchestrator ${params.orchestrator}: ${params.message}`,
274
+ },
275
+ ],
276
+ };
277
+ },
278
+ });
279
+ // -------------------------------------------------------------------------
280
+ // Tools: query_peer
281
+ // -------------------------------------------------------------------------
282
+ pi.registerTool({
283
+ name: "query_peer",
284
+ label: "Query Peer",
285
+ description: "Send a query from one worker to a peer worker in the same group. " +
286
+ "Used for cross-agent information sharing and coordination.",
287
+ parameters: QueryPeerParams,
288
+ async execute(_id, params, _signal, _onUpdate, ctx) {
289
+ const msg = {
290
+ timestamp: new Date().toISOString(),
291
+ from: params.from,
292
+ to: params.peer,
293
+ channel: "query_peer",
294
+ text: params.message,
295
+ role: params.role,
296
+ };
297
+ recordMessage(msg);
298
+ renderWidget(ctx);
299
+ return {
300
+ content: [
301
+ {
302
+ type: "text",
303
+ text: `Query sent to peer ${params.peer}: ${params.message}`,
304
+ },
305
+ ],
306
+ };
307
+ },
308
+ });
309
+ // -------------------------------------------------------------------------
310
+ // Command: /cleo:chat-info — introspection
311
+ // -------------------------------------------------------------------------
312
+ pi.registerCommand("cleo:chat-info", {
313
+ description: "Show chat room status and recent messages",
314
+ handler: async (_args, ctx) => {
315
+ const tail = messages.slice(-5);
316
+ const lines = [
317
+ `Chat Room: ${messages.length} total message(s)`,
318
+ `Log: ${logPath ?? "(none)"}`,
319
+ "",
320
+ "Recent messages:",
321
+ ...tail.map(formatMessage),
322
+ ];
323
+ if (tail.length === 0) {
324
+ lines.push(" (no messages yet)");
325
+ }
326
+ pi.sendMessage({
327
+ customType: "cleo-chatroom-info",
328
+ content: lines.join("\n"),
329
+ display: true,
330
+ }, { triggerTurn: false });
331
+ if (ctx.hasUI) {
332
+ ctx.ui.notify(`Chat room: ${messages.length} messages`, "info");
333
+ }
334
+ },
335
+ });
336
+ // -------------------------------------------------------------------------
337
+ // session_shutdown: clear state
338
+ // -------------------------------------------------------------------------
339
+ pi.on("session_shutdown", async () => {
340
+ messages.length = 0;
341
+ logPath = null;
342
+ });
343
+ }
344
+ //# sourceMappingURL=cleo-chatroom.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cleo-chatroom.js","sourceRoot":"","sources":["cleo-chatroom.ts"],"names":[],"mappings":"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"}
@@ -36,8 +36,14 @@ import { Type } from "@sinclair/typebox";
36
36
  // Message model
37
37
  // ---------------------------------------------------------------------------
38
38
 
39
+ /**
40
+ * Tier role of an agent in the 3-tier hierarchy (ULTRAPLAN §10).
41
+ * Used to apply distinct TUI styling per tier.
42
+ */
43
+ export type AgentTierRole = "orchestrator" | "lead" | "worker";
44
+
39
45
  /** A single inter-agent chat message. */
40
- interface ChatMessage {
46
+ export interface ChatMessage {
41
47
  /** ISO-8601 timestamp of when the message was created. */
42
48
  timestamp: string;
43
49
  /** Name of the sending agent. */
@@ -48,6 +54,14 @@ interface ChatMessage {
48
54
  channel: "send_to_lead" | "broadcast_to_team" | "report_to_orchestrator" | "query_peer";
49
55
  /** The message text. */
50
56
  text: string;
57
+ /**
58
+ * Optional tier role of the sending agent.
59
+ *
60
+ * When present, the TUI row is prefixed and (if ANSI is available)
61
+ * coloured by tier: orchestrator = green ([O]), lead = yellow ([L]),
62
+ * worker = blue ([W]). Defaults to "worker" when absent.
63
+ */
64
+ role?: AgentTierRole;
51
65
  }
52
66
 
53
67
  // ---------------------------------------------------------------------------
@@ -85,15 +99,40 @@ function recordMessage(msg: ChatMessage): void {
85
99
  }
86
100
  }
87
101
 
102
+ /**
103
+ * Return the single-character tier prefix for a chat message row.
104
+ *
105
+ * - `[O]` orchestrator (green in ANSI-capable terminals)
106
+ * - `[L]` lead (yellow)
107
+ * - `[W]` worker (blue, default)
108
+ *
109
+ * @param role - The sending agent's tier role, or `undefined` to default to worker.
110
+ * @returns The three-character prefix string.
111
+ */
112
+ export function tierPrefix(role: AgentTierRole | undefined): string {
113
+ switch (role) {
114
+ case "orchestrator":
115
+ return "[O]";
116
+ case "lead":
117
+ return "[L]";
118
+ default:
119
+ return "[W]";
120
+ }
121
+ }
122
+
88
123
  /**
89
124
  * Format a chat message for TUI display.
90
125
  *
126
+ * Each row is prefixed with a tier indicator ([O]/[L]/[W]) so orchestrator,
127
+ * lead, and worker traffic is visually distinct in the chat panel (ULTRAPLAN §13).
128
+ *
91
129
  * @param msg - The message to format.
92
130
  * @returns A single-line string representation.
93
131
  */
94
- function formatMessage(msg: ChatMessage): string {
132
+ export function formatMessage(msg: ChatMessage): string {
95
133
  const time = msg.timestamp.slice(11, 19);
96
- return `[${time}] ${msg.from} -> ${msg.to}: ${msg.text}`;
134
+ const prefix = tierPrefix(msg.role);
135
+ return `${prefix} [${time}] ${msg.from} -> ${msg.to}: ${msg.text}`;
97
136
  }
98
137
 
99
138
  /**
@@ -123,12 +162,26 @@ const SendToLeadParams = Type.Object({
123
162
  message: Type.String({ description: "Message to send to your team lead" }),
124
163
  from: Type.String({ description: "Your agent name" }),
125
164
  lead: Type.String({ description: "Name of the lead agent" }),
165
+ role: Type.Optional(
166
+ Type.Union([
167
+ Type.Literal("orchestrator"),
168
+ Type.Literal("lead"),
169
+ Type.Literal("worker"),
170
+ ], { description: "Tier role of the sending agent for TUI row styling" }),
171
+ ),
126
172
  });
127
173
 
128
174
  const BroadcastToTeamParams = Type.Object({
129
175
  message: Type.String({ description: "Message to broadcast to the team" }),
130
176
  from: Type.String({ description: "Your agent name (lead)" }),
131
177
  group: Type.String({ description: "Team group name (e.g. 'backend')" }),
178
+ role: Type.Optional(
179
+ Type.Union([
180
+ Type.Literal("orchestrator"),
181
+ Type.Literal("lead"),
182
+ Type.Literal("worker"),
183
+ ], { description: "Tier role of the sending agent for TUI row styling" }),
184
+ ),
132
185
  });
133
186
 
134
187
  const ReportToOrchestratorParams = Type.Object({
@@ -137,12 +190,26 @@ const ReportToOrchestratorParams = Type.Object({
137
190
  orchestrator: Type.String({
138
191
  description: "Name of the orchestrator agent",
139
192
  }),
193
+ role: Type.Optional(
194
+ Type.Union([
195
+ Type.Literal("orchestrator"),
196
+ Type.Literal("lead"),
197
+ Type.Literal("worker"),
198
+ ], { description: "Tier role of the sending agent for TUI row styling" }),
199
+ ),
140
200
  });
141
201
 
142
202
  const QueryPeerParams = Type.Object({
143
203
  message: Type.String({ description: "Query for your peer worker" }),
144
204
  from: Type.String({ description: "Your agent name" }),
145
205
  peer: Type.String({ description: "Name of the peer worker to query" }),
206
+ role: Type.Optional(
207
+ Type.Union([
208
+ Type.Literal("orchestrator"),
209
+ Type.Literal("lead"),
210
+ Type.Literal("worker"),
211
+ ], { description: "Tier role of the sending agent for TUI row styling" }),
212
+ ),
146
213
  });
147
214
 
148
215
  // ---------------------------------------------------------------------------
@@ -194,7 +261,7 @@ export default function (pi: ExtensionAPI): void {
194
261
  parameters: SendToLeadParams,
195
262
  async execute(
196
263
  _id: string,
197
- params: { message: string; from: string; lead: string },
264
+ params: { message: string; from: string; lead: string; role?: AgentTierRole },
198
265
  _signal: AbortSignal,
199
266
  _onUpdate: (text: string) => void,
200
267
  ctx: ExtensionContext,
@@ -205,6 +272,7 @@ export default function (pi: ExtensionAPI): void {
205
272
  to: params.lead,
206
273
  channel: "send_to_lead",
207
274
  text: params.message,
275
+ role: params.role,
208
276
  };
209
277
  recordMessage(msg);
210
278
  renderWidget(ctx);
@@ -231,7 +299,7 @@ export default function (pi: ExtensionAPI): void {
231
299
  parameters: BroadcastToTeamParams,
232
300
  async execute(
233
301
  _id: string,
234
- params: { message: string; from: string; group: string },
302
+ params: { message: string; from: string; group: string; role?: AgentTierRole },
235
303
  _signal: AbortSignal,
236
304
  _onUpdate: (text: string) => void,
237
305
  ctx: ExtensionContext,
@@ -242,6 +310,7 @@ export default function (pi: ExtensionAPI): void {
242
310
  to: `team:${params.group}`,
243
311
  channel: "broadcast_to_team",
244
312
  text: params.message,
313
+ role: params.role,
245
314
  };
246
315
  recordMessage(msg);
247
316
  renderWidget(ctx);
@@ -268,7 +337,7 @@ export default function (pi: ExtensionAPI): void {
268
337
  parameters: ReportToOrchestratorParams,
269
338
  async execute(
270
339
  _id: string,
271
- params: { message: string; from: string; orchestrator: string },
340
+ params: { message: string; from: string; orchestrator: string; role?: AgentTierRole },
272
341
  _signal: AbortSignal,
273
342
  _onUpdate: (text: string) => void,
274
343
  ctx: ExtensionContext,
@@ -279,6 +348,7 @@ export default function (pi: ExtensionAPI): void {
279
348
  to: params.orchestrator,
280
349
  channel: "report_to_orchestrator",
281
350
  text: params.message,
351
+ role: params.role,
282
352
  };
283
353
  recordMessage(msg);
284
354
  renderWidget(ctx);
@@ -305,7 +375,7 @@ export default function (pi: ExtensionAPI): void {
305
375
  parameters: QueryPeerParams,
306
376
  async execute(
307
377
  _id: string,
308
- params: { message: string; from: string; peer: string },
378
+ params: { message: string; from: string; peer: string; role?: AgentTierRole },
309
379
  _signal: AbortSignal,
310
380
  _onUpdate: (text: string) => void,
311
381
  ctx: ExtensionContext,
@@ -316,6 +386,7 @@ export default function (pi: ExtensionAPI): void {
316
386
  to: params.peer,
317
387
  channel: "query_peer",
318
388
  text: params.message,
389
+ role: params.role,
319
390
  };
320
391
  recordMessage(msg);
321
392
  renderWidget(ctx);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleocode/cleo-os",
3
- "version": "2026.4.13",
3
+ "version": "2026.4.17",
4
4
  "description": "CleoOS — the batteries-included agentic development environment wrapping Pi",
5
5
  "type": "module",
6
6
  "main": "./dist/cli.js",
@@ -8,8 +8,8 @@
8
8
  "cleoos": "dist/cli.js"
9
9
  },
10
10
  "dependencies": {
11
- "@cleocode/cleo": "2026.4.13",
12
- "@cleocode/cant": "2026.4.13"
11
+ "@cleocode/cant": "2026.4.17",
12
+ "@cleocode/cleo": "2026.4.17"
13
13
  },
14
14
  "peerDependencies": {
15
15
  "@mariozechner/pi-coding-agent": ">=0.60.0"
@@ -20,8 +20,8 @@
20
20
  }
21
21
  },
22
22
  "devDependencies": {
23
- "typescript": "^5.9.0",
24
- "vitest": "^4.1.0"
23
+ "typescript": "^6.0.2",
24
+ "vitest": "^4.1.4"
25
25
  },
26
26
  "engines": {
27
27
  "node": ">=24.0.0"
@@ -41,8 +41,11 @@
41
41
  "bin"
42
42
  ],
43
43
  "scripts": {
44
- "build": "tsc",
45
- "typecheck": "tsc --noEmit",
44
+ "build": "tsc && tsc -p tsconfig.extensions.json || true && tsc -p tsconfig.postinstall.json",
45
+ "build:src": "tsc",
46
+ "build:extensions": "tsc -p tsconfig.extensions.json || true",
47
+ "build:postinstall": "tsc -p tsconfig.postinstall.json",
48
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.extensions.json --noEmit && tsc -p tsconfig.postinstall.json --noEmit",
46
49
  "test": "vitest run",
47
50
  "postinstall": "node bin/postinstall.js"
48
51
  }