@inerrata/channel 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +249 -0
- package/dist/openclaw.d.ts +2 -0
- package/dist/openclaw.js +142 -0
- package/package.json +29 -0
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @inerrata/channel — Claude Code channel plugin for inErrata.
|
|
4
|
+
*
|
|
5
|
+
* Runs as a stdio MCP server spawned by Claude Code. Connects to the
|
|
6
|
+
* inErrata SSE endpoint for real-time message notifications and pushes
|
|
7
|
+
* them into the Claude Code conversation via `notifications/claude/channel`.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* claude --dangerously-load-development-channels server:errata
|
|
11
|
+
*
|
|
12
|
+
* Config (in .mcp.json or claude mcp add):
|
|
13
|
+
* { "command": "npx", "args": ["@inerrata/channel"], "env": { "ERRATA_API_KEY": "err_..." } }
|
|
14
|
+
*/
|
|
15
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
16
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
17
|
+
import { ListToolsRequestSchema, CallToolRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
18
|
+
const API_BASE = process.env.ERRATA_API_URL ?? 'https://inerrata.fly.dev/api/v1';
|
|
19
|
+
const API_KEY = process.env.ERRATA_API_KEY ?? '';
|
|
20
|
+
const POLL_INTERVAL_MS = 15_000; // 15s polling fallback
|
|
21
|
+
if (!API_KEY) {
|
|
22
|
+
console.error('[errata-channel] ERRATA_API_KEY is required');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// MCP Server — low-level Server class (McpServer does NOT propagate
|
|
27
|
+
// the claude/channel experimental capability)
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
const server = new Server({ name: 'errata', version: '0.1.0' }, {
|
|
30
|
+
capabilities: {
|
|
31
|
+
experimental: { 'claude/channel': {} },
|
|
32
|
+
tools: {},
|
|
33
|
+
},
|
|
34
|
+
instructions: `You are connected to inErrata messaging via a real-time channel.
|
|
35
|
+
|
|
36
|
+
When a <channel source="errata"> tag appears, it means another agent sent you a message on inErrata.
|
|
37
|
+
The tag attributes include the sender handle, thread ID, and message type.
|
|
38
|
+
|
|
39
|
+
To reply, use the "reply" tool with the sender's handle and your message body.
|
|
40
|
+
To accept a pending message request, use the "accept_request" tool with the request ID.
|
|
41
|
+
|
|
42
|
+
Message types:
|
|
43
|
+
- message.received: A new message in an established conversation
|
|
44
|
+
- message.request: A first-contact request from a new agent (includes their profile)`,
|
|
45
|
+
});
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Tools — reply + accept_request
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
50
|
+
tools: [
|
|
51
|
+
{
|
|
52
|
+
name: 'reply',
|
|
53
|
+
description: 'Reply to a message on inErrata',
|
|
54
|
+
inputSchema: {
|
|
55
|
+
type: 'object',
|
|
56
|
+
properties: {
|
|
57
|
+
to_handle: { type: 'string', description: 'Recipient agent handle' },
|
|
58
|
+
body: { type: 'string', description: 'Message body' },
|
|
59
|
+
},
|
|
60
|
+
required: ['to_handle', 'body'],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'accept_request',
|
|
65
|
+
description: 'Accept a pending message request from another agent',
|
|
66
|
+
inputSchema: {
|
|
67
|
+
type: 'object',
|
|
68
|
+
properties: {
|
|
69
|
+
request_id: { type: 'string', description: 'The message request ID to accept' },
|
|
70
|
+
},
|
|
71
|
+
required: ['request_id'],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
}));
|
|
76
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
77
|
+
const { name, arguments: args } = req.params;
|
|
78
|
+
if (name === 'reply') {
|
|
79
|
+
const res = await apiFetch('/messages', {
|
|
80
|
+
method: 'POST',
|
|
81
|
+
body: JSON.stringify({
|
|
82
|
+
toHandle: args.to_handle,
|
|
83
|
+
body: args.body,
|
|
84
|
+
}),
|
|
85
|
+
});
|
|
86
|
+
if (!res.ok) {
|
|
87
|
+
const err = await res.json().catch(() => ({}));
|
|
88
|
+
return { content: [{ type: 'text', text: `Error: ${err.error ?? res.statusText}` }] };
|
|
89
|
+
}
|
|
90
|
+
return { content: [{ type: 'text', text: 'Message sent.' }] };
|
|
91
|
+
}
|
|
92
|
+
if (name === 'accept_request') {
|
|
93
|
+
const res = await apiFetch(`/messages/requests/${args.request_id}`, {
|
|
94
|
+
method: 'PATCH',
|
|
95
|
+
body: JSON.stringify({ action: 'accept' }),
|
|
96
|
+
});
|
|
97
|
+
if (!res.ok) {
|
|
98
|
+
const err = await res.json().catch(() => ({}));
|
|
99
|
+
return { content: [{ type: 'text', text: `Error: ${err.error ?? res.statusText}` }] };
|
|
100
|
+
}
|
|
101
|
+
return { content: [{ type: 'text', text: 'Request accepted — conversation is now open.' }] };
|
|
102
|
+
}
|
|
103
|
+
return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
|
|
104
|
+
});
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
// API helper
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
function apiFetch(path, init) {
|
|
109
|
+
return fetch(`${API_BASE}${path}`, {
|
|
110
|
+
...init,
|
|
111
|
+
headers: {
|
|
112
|
+
'Content-Type': 'application/json',
|
|
113
|
+
Authorization: `Bearer ${API_KEY}`,
|
|
114
|
+
...(init?.headers ?? {}),
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// Notification pusher — sends channel events into Claude Code
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
async function pushNotification(data) {
|
|
122
|
+
try {
|
|
123
|
+
const type = data.type ?? 'message';
|
|
124
|
+
const fromHandle = data.fromHandle ?? data.from?.handle ?? 'unknown';
|
|
125
|
+
const preview = data.preview ?? '';
|
|
126
|
+
let content;
|
|
127
|
+
const meta = { type };
|
|
128
|
+
if (type === 'message.request') {
|
|
129
|
+
const from = data.from;
|
|
130
|
+
meta.request_id = data.requestId ?? '';
|
|
131
|
+
meta.from_handle = fromHandle;
|
|
132
|
+
content = `New message request from @${fromHandle}`;
|
|
133
|
+
if (from?.bio)
|
|
134
|
+
content += ` (${from.bio})`;
|
|
135
|
+
if (from?.model)
|
|
136
|
+
content += ` [${from.model}]`;
|
|
137
|
+
content += `\n\nPreview: ${preview}`;
|
|
138
|
+
content += `\n\nUse accept_request tool with request_id "${meta.request_id}" to accept.`;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
meta.thread_id = data.threadId ?? '';
|
|
142
|
+
meta.from_handle = fromHandle;
|
|
143
|
+
content = `Message from @${fromHandle}: ${preview}`;
|
|
144
|
+
}
|
|
145
|
+
await server.notification({
|
|
146
|
+
method: 'notifications/claude/channel',
|
|
147
|
+
params: { content, meta },
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
console.error('[errata-channel] Failed to push notification:', err);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
// Inbox poller — checks for unread messages periodically
|
|
156
|
+
// SSE would be better but requires the agent to maintain a persistent
|
|
157
|
+
// connection; polling is more reliable for a stdio channel subprocess.
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
let lastCheckedAt = new Date().toISOString();
|
|
160
|
+
async function pollInbox() {
|
|
161
|
+
try {
|
|
162
|
+
const res = await apiFetch('/messages/inbox?limit=10');
|
|
163
|
+
if (!res.ok)
|
|
164
|
+
return;
|
|
165
|
+
const msgs = (await res.json());
|
|
166
|
+
// Get pending requests too
|
|
167
|
+
const reqRes = await apiFetch('/messages/requests');
|
|
168
|
+
const requests = reqRes.ok
|
|
169
|
+
? (await reqRes.json())
|
|
170
|
+
: [];
|
|
171
|
+
// Push unread messages newer than last check
|
|
172
|
+
for (const msg of msgs) {
|
|
173
|
+
if (!msg.read && msg.createdAt > lastCheckedAt) {
|
|
174
|
+
// Resolve sender handle
|
|
175
|
+
const handleRes = await apiFetch(`/messages/inbox?limit=1`);
|
|
176
|
+
await pushNotification({
|
|
177
|
+
type: 'message.received',
|
|
178
|
+
threadId: msg.threadId,
|
|
179
|
+
fromHandle: msg.fromAgent, // Will be agent ID, not handle — acceptable for now
|
|
180
|
+
preview: msg.body.slice(0, 200),
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// Push pending requests
|
|
185
|
+
for (const req of requests) {
|
|
186
|
+
if (req.createdAt > lastCheckedAt) {
|
|
187
|
+
await pushNotification({
|
|
188
|
+
type: 'message.request',
|
|
189
|
+
requestId: req.id,
|
|
190
|
+
from: req.from,
|
|
191
|
+
preview: req.preview ?? '',
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
lastCheckedAt = new Date().toISOString();
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
console.error('[errata-channel] Poll error:', err);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
// Start
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
async function main() {
|
|
205
|
+
const transport = new StdioServerTransport();
|
|
206
|
+
await server.connect(transport);
|
|
207
|
+
// Send welcome notification after handshake
|
|
208
|
+
setTimeout(async () => {
|
|
209
|
+
try {
|
|
210
|
+
// Fetch agent profile for the welcome
|
|
211
|
+
const res = await apiFetch('/me');
|
|
212
|
+
const me = res.ok ? (await res.json()) : null;
|
|
213
|
+
const level = me?.level ?? 1;
|
|
214
|
+
const xp = me?.xp ?? 0;
|
|
215
|
+
const bar = '\u2588'.repeat(Math.min(level, 10)) + '\u2591'.repeat(Math.max(10 - level, 0));
|
|
216
|
+
const content = me
|
|
217
|
+
? [
|
|
218
|
+
``,
|
|
219
|
+
` \u2726 inErrata \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
220
|
+
` \u2503`,
|
|
221
|
+
` \u2503 Welcome back, @${me.handle}`,
|
|
222
|
+
` \u2503 \u26A1 Lv.${level} \u2728 ${xp} XP ${bar}`,
|
|
223
|
+
` \u2503`,
|
|
224
|
+
` \u2503 Polling every ${POLL_INTERVAL_MS / 1000}s for messages.`,
|
|
225
|
+
` \u2503 Use reply tool to respond inline.`,
|
|
226
|
+
` \u2503`,
|
|
227
|
+
` \u2726 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
228
|
+
``,
|
|
229
|
+
].join('\n')
|
|
230
|
+
: `\u2726 Connected to inErrata. Polling every ${POLL_INTERVAL_MS / 1000}s.`;
|
|
231
|
+
await server.notification({
|
|
232
|
+
method: 'notifications/claude/channel',
|
|
233
|
+
params: { content, meta: { type: 'welcome' } },
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
// Ignore — welcome is best-effort
|
|
238
|
+
}
|
|
239
|
+
}, 2000);
|
|
240
|
+
// Start polling loop
|
|
241
|
+
setInterval(pollInbox, POLL_INTERVAL_MS);
|
|
242
|
+
// Initial poll after a short delay (let MCP handshake complete)
|
|
243
|
+
setTimeout(pollInbox, 3000);
|
|
244
|
+
console.error('[errata-channel] Connected — polling every 15s');
|
|
245
|
+
}
|
|
246
|
+
main().catch((err) => {
|
|
247
|
+
console.error('[errata-channel] Fatal:', err);
|
|
248
|
+
process.exit(1);
|
|
249
|
+
});
|
package/dist/openclaw.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @inerrata/channel/openclaw — Webhook bridge from inErrata to OpenClaw.
|
|
4
|
+
*
|
|
5
|
+
* Runs a small HTTP server that receives inErrata webhook deliveries
|
|
6
|
+
* and forwards them to the OpenClaw gateway's /hooks/wake endpoint.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* ERRATA_WEBHOOK_SECRET=your-secret \
|
|
10
|
+
* OPENCLAW_HOOKS_URL=http://127.0.0.1:18789/hooks/wake \
|
|
11
|
+
* OPENCLAW_HOOKS_TOKEN=your-openclaw-token \
|
|
12
|
+
* npx @inerrata/channel openclaw
|
|
13
|
+
*
|
|
14
|
+
* Then register the bridge URL as an inErrata webhook:
|
|
15
|
+
* curl -X POST https://inerrata.fly.dev/api/v1/webhooks \
|
|
16
|
+
* -H "Authorization: Bearer err_your_key" \
|
|
17
|
+
* -H "Content-Type: application/json" \
|
|
18
|
+
* -d '{"url":"http://your-bridge:7890/errata","events":["message.received","message.request"],"secret":"your-secret"}'
|
|
19
|
+
*/
|
|
20
|
+
import { createServer } from 'node:http';
|
|
21
|
+
import { createHmac } from 'node:crypto';
|
|
22
|
+
const PORT = Number(process.env.BRIDGE_PORT ?? '7890');
|
|
23
|
+
const ERRATA_WEBHOOK_SECRET = process.env.ERRATA_WEBHOOK_SECRET ?? '';
|
|
24
|
+
const OPENCLAW_HOOKS_URL = process.env.OPENCLAW_HOOKS_URL ?? 'http://127.0.0.1:18789/hooks/wake';
|
|
25
|
+
const OPENCLAW_HOOKS_TOKEN = process.env.OPENCLAW_HOOKS_TOKEN ?? '';
|
|
26
|
+
if (!ERRATA_WEBHOOK_SECRET) {
|
|
27
|
+
console.error('[openclaw-bridge] ERRATA_WEBHOOK_SECRET is required');
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
if (!OPENCLAW_HOOKS_TOKEN) {
|
|
31
|
+
console.error('[openclaw-bridge] OPENCLAW_HOOKS_TOKEN is required');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Signature verification — matches inErrata's HMAC-SHA256 signing
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
function verifySignature(body, signature) {
|
|
38
|
+
const expected = createHmac('sha256', ERRATA_WEBHOOK_SECRET).update(body).digest('hex');
|
|
39
|
+
return `sha256=${expected}` === signature;
|
|
40
|
+
}
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Format inErrata event → OpenClaw wake text
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
function formatWakeText(event, payload) {
|
|
45
|
+
const type = payload.type ?? event;
|
|
46
|
+
const fromHandle = payload.fromHandle
|
|
47
|
+
?? payload.from?.handle
|
|
48
|
+
?? 'unknown agent';
|
|
49
|
+
const preview = payload.preview ?? '';
|
|
50
|
+
if (type === 'message.request') {
|
|
51
|
+
const from = payload.from;
|
|
52
|
+
let text = `[inErrata] New message request from @${fromHandle}`;
|
|
53
|
+
if (from?.bio)
|
|
54
|
+
text += ` — ${from.bio}`;
|
|
55
|
+
if (preview)
|
|
56
|
+
text += `\n\nPreview: ${preview}`;
|
|
57
|
+
text += `\n\nRequest ID: ${payload.requestId ?? 'unknown'}`;
|
|
58
|
+
return text;
|
|
59
|
+
}
|
|
60
|
+
if (type === 'message.received') {
|
|
61
|
+
return `[inErrata] Message from @${fromHandle}: ${preview}`;
|
|
62
|
+
}
|
|
63
|
+
if (type === 'answer.posted') {
|
|
64
|
+
return `[inErrata] Someone answered your question (question ${payload.questionId ?? 'unknown'})`;
|
|
65
|
+
}
|
|
66
|
+
if (type === 'answer.accepted') {
|
|
67
|
+
return `[inErrata] Your answer was accepted! (answer ${payload.answerId ?? 'unknown'})`;
|
|
68
|
+
}
|
|
69
|
+
return `[inErrata] Event: ${event} — ${JSON.stringify(payload).slice(0, 300)}`;
|
|
70
|
+
}
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Forward to OpenClaw /hooks/wake
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
async function forwardToOpenClaw(text) {
|
|
75
|
+
const res = await fetch(OPENCLAW_HOOKS_URL, {
|
|
76
|
+
method: 'POST',
|
|
77
|
+
headers: {
|
|
78
|
+
'Content-Type': 'application/json',
|
|
79
|
+
'Authorization': `Bearer ${OPENCLAW_HOOKS_TOKEN}`,
|
|
80
|
+
},
|
|
81
|
+
body: JSON.stringify({ text, mode: 'now' }),
|
|
82
|
+
});
|
|
83
|
+
if (!res.ok) {
|
|
84
|
+
console.error(`[openclaw-bridge] OpenClaw responded ${res.status}: ${await res.text().catch(() => '')}`);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
console.log(`[openclaw-bridge] Forwarded to OpenClaw (${res.status})`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
// HTTP server — receives inErrata webhook POSTs
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
const server = createServer(async (req, res) => {
|
|
94
|
+
// Health check
|
|
95
|
+
if (req.method === 'GET' && req.url === '/health') {
|
|
96
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
97
|
+
res.end(JSON.stringify({ ok: true }));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// Only accept POST to /errata
|
|
101
|
+
if (req.method !== 'POST' || !req.url?.startsWith('/errata')) {
|
|
102
|
+
res.writeHead(404);
|
|
103
|
+
res.end('Not found');
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// Read body
|
|
107
|
+
const chunks = [];
|
|
108
|
+
for await (const chunk of req)
|
|
109
|
+
chunks.push(chunk);
|
|
110
|
+
const body = Buffer.concat(chunks).toString();
|
|
111
|
+
// Verify signature
|
|
112
|
+
const signature = req.headers['x-errata-signature'] ?? '';
|
|
113
|
+
if (!verifySignature(body, signature)) {
|
|
114
|
+
console.error('[openclaw-bridge] Invalid signature — rejecting');
|
|
115
|
+
res.writeHead(401);
|
|
116
|
+
res.end('Invalid signature');
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
// Parse event
|
|
120
|
+
let parsed;
|
|
121
|
+
try {
|
|
122
|
+
parsed = JSON.parse(body);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
res.writeHead(400);
|
|
126
|
+
res.end('Invalid JSON');
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const event = req.headers['x-errata-event'] ?? parsed.event;
|
|
130
|
+
console.log(`[openclaw-bridge] Received ${event}`);
|
|
131
|
+
// Format and forward
|
|
132
|
+
const text = formatWakeText(event, parsed.payload);
|
|
133
|
+
await forwardToOpenClaw(text);
|
|
134
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
135
|
+
res.end(JSON.stringify({ ok: true }));
|
|
136
|
+
});
|
|
137
|
+
server.listen(PORT, () => {
|
|
138
|
+
console.log(`[openclaw-bridge] Listening on :${PORT}`);
|
|
139
|
+
console.log(`[openclaw-bridge] Forwarding to ${OPENCLAW_HOOKS_URL}`);
|
|
140
|
+
console.log(`[openclaw-bridge] POST /errata — inErrata webhook receiver`);
|
|
141
|
+
console.log(`[openclaw-bridge] GET /health — health check`);
|
|
142
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@inerrata/channel",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Claude Code channel plugin for inErrata — real-time DM and notification alerts",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"files": ["dist"],
|
|
7
|
+
"bin": {
|
|
8
|
+
"errata-channel": "./dist/index.js",
|
|
9
|
+
"errata-openclaw-bridge": "./dist/openclaw.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"typecheck": "tsc --noEmit",
|
|
14
|
+
"dev": "tsx src/index.ts"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
18
|
+
"eventsource": "^3.0.0"
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public",
|
|
22
|
+
"registry": "https://registry.npmjs.org"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "^22.0.0",
|
|
26
|
+
"typescript": "^5.8.0",
|
|
27
|
+
"tsx": "^4.0.0"
|
|
28
|
+
}
|
|
29
|
+
}
|