botinabox 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/CONTRIBUTING.md +92 -0
- package/LICENSE +21 -0
- package/README.md +245 -0
- package/bin/botinabox.mjs +2 -0
- package/dist/channel-m9f7MFD7.d.ts +71 -0
- package/dist/channels/discord/index.d.ts +86 -0
- package/dist/channels/discord/index.js +98 -0
- package/dist/channels/slack/index.d.ts +81 -0
- package/dist/channels/slack/index.js +80 -0
- package/dist/channels/webhook/index.d.ts +72 -0
- package/dist/channels/webhook/index.js +178 -0
- package/dist/chunk-DLJKZD3Q.js +22 -0
- package/dist/chunk-QLA6YOFN.js +22 -0
- package/dist/inbound-AFOHYNUY.js +6 -0
- package/dist/inbound-SNEMBLGA.js +6 -0
- package/dist/index.d.ts +1268 -0
- package/dist/index.js +2965 -0
- package/dist/provider-qqJYv9nv.d.ts +75 -0
- package/dist/providers/anthropic/index.d.ts +22 -0
- package/dist/providers/anthropic/index.js +169 -0
- package/dist/providers/ollama/index.d.ts +24 -0
- package/dist/providers/ollama/index.js +179 -0
- package/dist/providers/openai/index.d.ts +22 -0
- package/dist/providers/openai/index.js +212 -0
- package/package.json +72 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseSlackEvent
|
|
3
|
+
} from "../../chunk-QLA6YOFN.js";
|
|
4
|
+
|
|
5
|
+
// src/channels/slack/outbound.ts
|
|
6
|
+
function formatForSlack(text) {
|
|
7
|
+
let result = text.replace(/\*\*(.+?)\*\*/gs, "*$1*");
|
|
8
|
+
result = result.replace(/__(.+?)__/gs, "*$1*");
|
|
9
|
+
return result;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// src/channels/slack/adapter.ts
|
|
13
|
+
var SlackAdapter = class {
|
|
14
|
+
id = "slack";
|
|
15
|
+
meta = {
|
|
16
|
+
displayName: "Slack",
|
|
17
|
+
icon: "https://slack.com/favicon.ico",
|
|
18
|
+
homepage: "https://slack.com"
|
|
19
|
+
};
|
|
20
|
+
capabilities = {
|
|
21
|
+
chatTypes: ["direct", "group", "channel"],
|
|
22
|
+
threads: true,
|
|
23
|
+
reactions: true,
|
|
24
|
+
editing: true,
|
|
25
|
+
media: true,
|
|
26
|
+
polls: false,
|
|
27
|
+
maxTextLength: 4e4,
|
|
28
|
+
formattingMode: "mrkdwn"
|
|
29
|
+
};
|
|
30
|
+
onMessage;
|
|
31
|
+
connected = false;
|
|
32
|
+
config = null;
|
|
33
|
+
client;
|
|
34
|
+
constructor(client) {
|
|
35
|
+
this.client = client ?? null;
|
|
36
|
+
}
|
|
37
|
+
async connect(config) {
|
|
38
|
+
this.config = config;
|
|
39
|
+
this.connected = true;
|
|
40
|
+
}
|
|
41
|
+
async disconnect() {
|
|
42
|
+
this.connected = false;
|
|
43
|
+
this.config = null;
|
|
44
|
+
}
|
|
45
|
+
async healthCheck() {
|
|
46
|
+
return { ok: this.connected };
|
|
47
|
+
}
|
|
48
|
+
async send(target, payload) {
|
|
49
|
+
if (!this.connected) {
|
|
50
|
+
return { success: false, error: "Not connected" };
|
|
51
|
+
}
|
|
52
|
+
const text = formatForSlack(payload.text);
|
|
53
|
+
if (this.client) {
|
|
54
|
+
try {
|
|
55
|
+
const result = await this.client.postMessage(target.peerId, text, target.threadId);
|
|
56
|
+
return { success: result.ok, messageId: result.ts };
|
|
57
|
+
} catch (err) {
|
|
58
|
+
return { success: false, error: String(err) };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return { success: true };
|
|
62
|
+
}
|
|
63
|
+
/** Simulate receiving an inbound message (for testing/webhooks). */
|
|
64
|
+
async receive(event) {
|
|
65
|
+
if (this.onMessage) {
|
|
66
|
+
const { parseSlackEvent: parseSlackEvent2 } = await import("../../inbound-AFOHYNUY.js");
|
|
67
|
+
const msg = parseSlackEvent2(event);
|
|
68
|
+
await this.onMessage(msg);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
function createSlackAdapter(client) {
|
|
73
|
+
return new SlackAdapter(client);
|
|
74
|
+
}
|
|
75
|
+
export {
|
|
76
|
+
SlackAdapter,
|
|
77
|
+
createSlackAdapter as default,
|
|
78
|
+
formatForSlack,
|
|
79
|
+
parseSlackEvent
|
|
80
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { C as ChannelAdapter, c as ChannelMeta, a as ChannelCapabilities, I as InboundMessage, b as ChannelConfig, H as HealthStatus, O as OutboundPayload, S as SendResult } from '../../channel-m9f7MFD7.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* WebhookAdapter — ChannelAdapter implementation for webhook-based channels.
|
|
5
|
+
* Story 4.7
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
declare class WebhookAdapter implements ChannelAdapter {
|
|
9
|
+
readonly id = "webhook";
|
|
10
|
+
readonly meta: ChannelMeta;
|
|
11
|
+
readonly capabilities: ChannelCapabilities;
|
|
12
|
+
onMessage?: (message: InboundMessage) => Promise<void>;
|
|
13
|
+
private connected;
|
|
14
|
+
private config;
|
|
15
|
+
private webhookServer;
|
|
16
|
+
connect(config: ChannelConfig): Promise<void>;
|
|
17
|
+
disconnect(): Promise<void>;
|
|
18
|
+
healthCheck(): Promise<HealthStatus>;
|
|
19
|
+
send(target: {
|
|
20
|
+
peerId: string;
|
|
21
|
+
threadId?: string;
|
|
22
|
+
}, payload: OutboundPayload): Promise<SendResult>;
|
|
23
|
+
}
|
|
24
|
+
/** Factory function — default export for auto-discovery. */
|
|
25
|
+
declare function createWebhookAdapter(): WebhookAdapter;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Minimal HTTP server for webhook inbound messages.
|
|
29
|
+
* Story 4.7
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
interface WebhookServerOpts {
|
|
33
|
+
port?: number;
|
|
34
|
+
secret?: string;
|
|
35
|
+
onMessage: (msg: InboundMessage) => Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
declare class WebhookServer {
|
|
38
|
+
private server;
|
|
39
|
+
private readonly port;
|
|
40
|
+
private readonly secret;
|
|
41
|
+
private readonly onMessage;
|
|
42
|
+
constructor(opts: WebhookServerOpts);
|
|
43
|
+
start(): Promise<void>;
|
|
44
|
+
stop(): Promise<void>;
|
|
45
|
+
private handleRequest;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* HMAC signature verification for webhook payloads.
|
|
50
|
+
* Story 4.7
|
|
51
|
+
*/
|
|
52
|
+
/**
|
|
53
|
+
* Verify that the HMAC-SHA256 signature of body matches the provided signature.
|
|
54
|
+
* Uses timing-safe comparison to prevent timing attacks.
|
|
55
|
+
*
|
|
56
|
+
* @param body - The raw request body string
|
|
57
|
+
* @param secret - The shared HMAC secret
|
|
58
|
+
* @param signature - The signature to verify (hex string, possibly prefixed with "sha256=")
|
|
59
|
+
*/
|
|
60
|
+
declare function verifyHmac(body: string, secret: string, signature: string): boolean;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Webhook channel configuration types.
|
|
64
|
+
* Story 4.7
|
|
65
|
+
*/
|
|
66
|
+
interface WebhookConfig {
|
|
67
|
+
callbackUrl?: string;
|
|
68
|
+
secret?: string;
|
|
69
|
+
port?: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export { WebhookAdapter, type WebhookConfig, WebhookServer, type WebhookServerOpts, createWebhookAdapter as default, verifyHmac };
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// src/channels/webhook/server.ts
|
|
2
|
+
import { createServer } from "http";
|
|
3
|
+
|
|
4
|
+
// src/channels/webhook/hmac.ts
|
|
5
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
6
|
+
function verifyHmac(body, secret, signature) {
|
|
7
|
+
const expected = createHmac("sha256", secret).update(body, "utf8").digest("hex");
|
|
8
|
+
const provided = signature.startsWith("sha256=") ? signature.slice(7) : signature;
|
|
9
|
+
if (expected.length !== provided.length) return false;
|
|
10
|
+
try {
|
|
11
|
+
return timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(provided, "hex"));
|
|
12
|
+
} catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// src/channels/webhook/server.ts
|
|
18
|
+
var WebhookServer = class {
|
|
19
|
+
server = null;
|
|
20
|
+
port;
|
|
21
|
+
secret;
|
|
22
|
+
onMessage;
|
|
23
|
+
constructor(opts) {
|
|
24
|
+
this.port = opts.port ?? 3200;
|
|
25
|
+
this.secret = opts.secret;
|
|
26
|
+
this.onMessage = opts.onMessage;
|
|
27
|
+
}
|
|
28
|
+
start() {
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
this.server = createServer((req, res) => {
|
|
31
|
+
void this.handleRequest(req, res);
|
|
32
|
+
});
|
|
33
|
+
this.server.listen(this.port, () => resolve());
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
stop() {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
if (!this.server) {
|
|
39
|
+
resolve();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
this.server.close((err) => {
|
|
43
|
+
if (err) reject(err);
|
|
44
|
+
else resolve();
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
async handleRequest(req, res) {
|
|
49
|
+
const url = req.url ?? "/";
|
|
50
|
+
const method = req.method ?? "GET";
|
|
51
|
+
if (method !== "POST" || url !== "/webhook/inbound") {
|
|
52
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
53
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
let body = "";
|
|
57
|
+
for await (const chunk of req) {
|
|
58
|
+
body += chunk;
|
|
59
|
+
}
|
|
60
|
+
if (this.secret) {
|
|
61
|
+
const sig = req.headers["x-webhook-signature"];
|
|
62
|
+
if (!sig || !verifyHmac(body, this.secret, sig)) {
|
|
63
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
64
|
+
res.end(JSON.stringify({ error: "Invalid signature" }));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
let parsed;
|
|
69
|
+
try {
|
|
70
|
+
parsed = JSON.parse(body);
|
|
71
|
+
} catch {
|
|
72
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
73
|
+
res.end(JSON.stringify({ error: "Invalid JSON" }));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const msg = {
|
|
77
|
+
id: parsed["id"] ?? `webhook-${Date.now()}`,
|
|
78
|
+
channel: "webhook",
|
|
79
|
+
from: parsed["from"] ?? "unknown",
|
|
80
|
+
body: parsed["text"] ?? "",
|
|
81
|
+
threadId: parsed["threadId"],
|
|
82
|
+
receivedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
83
|
+
raw: parsed
|
|
84
|
+
};
|
|
85
|
+
try {
|
|
86
|
+
await this.onMessage(msg);
|
|
87
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
88
|
+
res.end(JSON.stringify({ ok: true }));
|
|
89
|
+
} catch (err) {
|
|
90
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
91
|
+
res.end(JSON.stringify({ error: String(err) }));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// src/channels/webhook/adapter.ts
|
|
97
|
+
var WebhookAdapter = class {
|
|
98
|
+
id = "webhook";
|
|
99
|
+
meta = {
|
|
100
|
+
displayName: "Webhook",
|
|
101
|
+
homepage: "https://example.com"
|
|
102
|
+
};
|
|
103
|
+
capabilities = {
|
|
104
|
+
chatTypes: ["direct"],
|
|
105
|
+
threads: false,
|
|
106
|
+
reactions: false,
|
|
107
|
+
editing: false,
|
|
108
|
+
media: false,
|
|
109
|
+
polls: false,
|
|
110
|
+
maxTextLength: 65535,
|
|
111
|
+
formattingMode: "plain"
|
|
112
|
+
};
|
|
113
|
+
onMessage;
|
|
114
|
+
connected = false;
|
|
115
|
+
config = null;
|
|
116
|
+
webhookServer = null;
|
|
117
|
+
async connect(config) {
|
|
118
|
+
this.config = config;
|
|
119
|
+
this.connected = true;
|
|
120
|
+
if (this.config.port) {
|
|
121
|
+
this.webhookServer = new WebhookServer({
|
|
122
|
+
port: this.config.port,
|
|
123
|
+
secret: this.config.secret,
|
|
124
|
+
onMessage: async (msg) => {
|
|
125
|
+
if (this.onMessage) await this.onMessage(msg);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
await this.webhookServer.start();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async disconnect() {
|
|
132
|
+
if (this.webhookServer) {
|
|
133
|
+
await this.webhookServer.stop();
|
|
134
|
+
this.webhookServer = null;
|
|
135
|
+
}
|
|
136
|
+
this.connected = false;
|
|
137
|
+
this.config = null;
|
|
138
|
+
}
|
|
139
|
+
async healthCheck() {
|
|
140
|
+
return { ok: this.connected };
|
|
141
|
+
}
|
|
142
|
+
async send(target, payload) {
|
|
143
|
+
if (!this.connected) {
|
|
144
|
+
return { success: false, error: "Not connected" };
|
|
145
|
+
}
|
|
146
|
+
const callbackUrl = this.config?.callbackUrl;
|
|
147
|
+
if (!callbackUrl) {
|
|
148
|
+
return { success: true };
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
const body = JSON.stringify({
|
|
152
|
+
to: target.peerId,
|
|
153
|
+
threadId: target.threadId,
|
|
154
|
+
text: payload.text
|
|
155
|
+
});
|
|
156
|
+
const response = await fetch(callbackUrl, {
|
|
157
|
+
method: "POST",
|
|
158
|
+
headers: { "Content-Type": "application/json" },
|
|
159
|
+
body
|
|
160
|
+
});
|
|
161
|
+
if (response.ok) {
|
|
162
|
+
return { success: true };
|
|
163
|
+
}
|
|
164
|
+
return { success: false, error: `HTTP ${response.status}` };
|
|
165
|
+
} catch (err) {
|
|
166
|
+
return { success: false, error: String(err) };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
function createWebhookAdapter() {
|
|
171
|
+
return new WebhookAdapter();
|
|
172
|
+
}
|
|
173
|
+
export {
|
|
174
|
+
WebhookAdapter,
|
|
175
|
+
WebhookServer,
|
|
176
|
+
createWebhookAdapter as default,
|
|
177
|
+
verifyHmac
|
|
178
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// src/channels/discord/inbound.ts
|
|
2
|
+
function parseDiscordEvent(event) {
|
|
3
|
+
const id = event.id ?? `discord-${Date.now()}`;
|
|
4
|
+
const channel = event.channel_id ?? "unknown";
|
|
5
|
+
const from = event.author?.id ?? "unknown";
|
|
6
|
+
const body = event.content ?? "";
|
|
7
|
+
const replyToId = event.message_reference?.message_id;
|
|
8
|
+
const receivedAt = event.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
9
|
+
return {
|
|
10
|
+
id,
|
|
11
|
+
channel,
|
|
12
|
+
from,
|
|
13
|
+
body,
|
|
14
|
+
replyToId,
|
|
15
|
+
receivedAt,
|
|
16
|
+
raw: event
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export {
|
|
21
|
+
parseDiscordEvent
|
|
22
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// src/channels/slack/inbound.ts
|
|
2
|
+
function parseSlackEvent(event) {
|
|
3
|
+
const id = event.client_msg_id ?? event.ts ?? event.event_ts ?? `slack-${Date.now()}`;
|
|
4
|
+
const channel = event.channel ?? "unknown";
|
|
5
|
+
const from = event.user ?? "unknown";
|
|
6
|
+
const body = event.text ?? "";
|
|
7
|
+
const threadId = event.thread_ts !== void 0 ? event.thread_ts : void 0;
|
|
8
|
+
const receivedAt = event.ts ? new Date(parseFloat(event.ts) * 1e3).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
|
|
9
|
+
return {
|
|
10
|
+
id,
|
|
11
|
+
channel,
|
|
12
|
+
from,
|
|
13
|
+
body,
|
|
14
|
+
threadId,
|
|
15
|
+
receivedAt,
|
|
16
|
+
raw: event
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export {
|
|
21
|
+
parseSlackEvent
|
|
22
|
+
};
|