botinabox 1.8.2 → 1.8.3
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/channels/discord/adapter.d.ts +32 -0
- package/dist/channels/discord/adapter.js +70 -0
- package/dist/channels/discord/inbound.d.ts +25 -0
- package/dist/channels/discord/inbound.js +24 -0
- package/dist/channels/discord/models.d.ts +8 -0
- package/dist/channels/discord/models.js +5 -0
- package/dist/channels/discord/outbound.d.ts +14 -0
- package/dist/channels/discord/outbound.js +38 -0
- package/dist/channels/slack/adapter.d.ts +33 -0
- package/dist/channels/slack/adapter.js +74 -0
- package/dist/channels/slack/inbound.d.ts +59 -0
- package/dist/channels/slack/inbound.js +96 -0
- package/dist/channels/slack/models.d.ts +9 -0
- package/dist/channels/slack/models.js +5 -0
- package/dist/channels/slack/outbound.d.ts +12 -0
- package/dist/channels/slack/outbound.js +18 -0
- package/dist/channels/slack/transcribe.d.ts +41 -0
- package/dist/channels/slack/transcribe.js +106 -0
- package/dist/channels/webhook/adapter.d.ts +23 -0
- package/dist/channels/webhook/adapter.js +86 -0
- package/dist/channels/webhook/hmac.d.ts +13 -0
- package/dist/channels/webhook/hmac.js +26 -0
- package/dist/channels/webhook/models.d.ts +9 -0
- package/dist/channels/webhook/models.js +5 -0
- package/dist/channels/webhook/server.d.ts +20 -0
- package/dist/channels/webhook/server.js +91 -0
- package/dist/cli/templates/config.yml.d.ts +7 -0
- package/dist/cli/templates/config.yml.js +61 -0
- package/dist/cli/templates/env.d.ts +1 -0
- package/dist/cli/templates/env.js +30 -0
- package/dist/cli/templates/index.ts.d.ts +2 -0
- package/dist/cli/templates/index.ts.js +30 -0
- package/dist/cli/templates/package.json.d.ts +5 -0
- package/dist/cli/templates/package.json.js +28 -0
- package/dist/connectors/google/calendar-connector.d.ts +40 -0
- package/dist/connectors/google/calendar-connector.js +243 -0
- package/dist/connectors/google/gmail-connector.d.ts +42 -0
- package/dist/connectors/google/gmail-connector.js +345 -0
- package/dist/connectors/google/oauth.d.ts +48 -0
- package/dist/connectors/google/oauth.js +112 -0
- package/dist/connectors/google/types.d.ts +78 -0
- package/dist/connectors/google/types.js +2 -0
- package/dist/core/chat/auto-discovery.d.ts +16 -0
- package/dist/core/chat/auto-discovery.js +54 -0
- package/dist/core/chat/channel-registry.d.ts +45 -0
- package/dist/core/chat/channel-registry.js +96 -0
- package/dist/core/chat/chat-pipeline.d.ts +104 -0
- package/dist/core/chat/chat-pipeline.js +282 -0
- package/dist/core/chat/chat-responder.d.ts +90 -0
- package/dist/core/chat/chat-responder.js +185 -0
- package/dist/core/chat/formatter.d.ts +11 -0
- package/dist/core/chat/formatter.js +60 -0
- package/dist/core/chat/index.d.ts +24 -0
- package/dist/core/chat/index.js +18 -0
- package/dist/core/chat/message-interpreter.d.ts +91 -0
- package/dist/core/chat/message-interpreter.js +164 -0
- package/dist/core/chat/message-store.d.ts +66 -0
- package/dist/core/chat/message-store.js +131 -0
- package/dist/core/chat/notification-queue.d.ts +34 -0
- package/dist/core/chat/notification-queue.js +111 -0
- package/dist/core/chat/pipeline.d.ts +38 -0
- package/dist/core/chat/pipeline.js +89 -0
- package/dist/core/chat/policies.d.ts +16 -0
- package/dist/core/chat/policies.js +25 -0
- package/dist/core/chat/routing.d.ts +17 -0
- package/dist/core/chat/routing.js +36 -0
- package/dist/core/chat/session-key.d.ts +30 -0
- package/dist/core/chat/session-key.js +65 -0
- package/dist/core/chat/session-manager.d.ts +17 -0
- package/dist/core/chat/session-manager.js +23 -0
- package/dist/core/chat/text-chunker.d.ts +9 -0
- package/dist/core/chat/text-chunker.js +48 -0
- package/dist/core/chat/triage-router.d.ts +75 -0
- package/dist/core/chat/triage-router.js +142 -0
- package/dist/core/chat/types.d.ts +5 -0
- package/dist/core/chat/types.js +5 -0
- package/dist/core/config/defaults.d.ts +2 -0
- package/dist/core/config/defaults.js +38 -0
- package/dist/core/config/index.d.ts +6 -0
- package/dist/core/config/index.js +4 -0
- package/dist/core/config/interpolate.d.ts +5 -0
- package/dist/core/config/interpolate.js +27 -0
- package/dist/core/config/loader.d.ts +24 -0
- package/dist/core/config/loader.js +59 -0
- package/dist/core/config/schema.d.ts +5 -0
- package/dist/core/config/schema.js +119 -0
- package/dist/core/data/core-entity-contexts.d.ts +14 -0
- package/dist/core/data/core-entity-contexts.js +197 -0
- package/dist/core/data/core-migrations.d.ts +5 -0
- package/dist/core/data/core-migrations.js +45 -0
- package/dist/core/data/core-schema.d.ts +6 -0
- package/dist/core/data/core-schema.js +454 -0
- package/dist/core/data/data-store.d.ts +67 -0
- package/dist/core/data/data-store.js +218 -0
- package/dist/core/data/domain-entity-contexts.d.ts +29 -0
- package/dist/core/data/domain-entity-contexts.js +321 -0
- package/dist/core/data/domain-schema.d.ts +36 -0
- package/dist/core/data/domain-schema.js +323 -0
- package/dist/core/data/index.d.ts +7 -0
- package/dist/core/data/index.js +7 -0
- package/dist/core/data/types.d.ts +111 -0
- package/dist/core/data/types.js +1 -0
- package/dist/core/hooks/hook-bus.d.ts +18 -0
- package/dist/core/hooks/hook-bus.js +120 -0
- package/dist/core/hooks/index.d.ts +2 -0
- package/dist/core/hooks/index.js +1 -0
- package/dist/core/hooks/types.d.ts +19 -0
- package/dist/core/hooks/types.js +1 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.js +4 -0
- package/dist/core/llm/auto-discovery.d.ts +11 -0
- package/dist/core/llm/auto-discovery.js +49 -0
- package/dist/core/llm/cost-tracker.d.ts +6 -0
- package/dist/core/llm/cost-tracker.js +38 -0
- package/dist/core/llm/index.d.ts +4 -0
- package/dist/core/llm/index.js +3 -0
- package/dist/core/llm/model-router.d.ts +25 -0
- package/dist/core/llm/model-router.js +49 -0
- package/dist/core/llm/provider-registry.d.ts +9 -0
- package/dist/core/llm/provider-registry.js +25 -0
- package/dist/core/llm/types.d.ts +2 -0
- package/dist/core/llm/types.js +2 -0
- package/dist/core/orchestrator/adapters/api-adapter.d.ts +34 -0
- package/dist/core/orchestrator/adapters/api-adapter.js +88 -0
- package/dist/core/orchestrator/adapters/cli-adapter.d.ts +22 -0
- package/dist/core/orchestrator/adapters/cli-adapter.js +69 -0
- package/dist/core/orchestrator/adapters/deterministic-adapter.d.ts +35 -0
- package/dist/core/orchestrator/adapters/deterministic-adapter.js +75 -0
- package/dist/core/orchestrator/adapters/env-whitelist.d.ts +4 -0
- package/dist/core/orchestrator/adapters/env-whitelist.js +27 -0
- package/dist/core/orchestrator/adapters/output-extractor.d.ts +11 -0
- package/dist/core/orchestrator/adapters/output-extractor.js +59 -0
- package/dist/core/orchestrator/adapters/process-manager.d.ts +15 -0
- package/dist/core/orchestrator/adapters/process-manager.js +26 -0
- package/dist/core/orchestrator/adapters/tool-loop.d.ts +22 -0
- package/dist/core/orchestrator/adapters/tool-loop.js +66 -0
- package/dist/core/orchestrator/agent-registry.d.ts +31 -0
- package/dist/core/orchestrator/agent-registry.js +135 -0
- package/dist/core/orchestrator/budget-controller.d.ts +19 -0
- package/dist/core/orchestrator/budget-controller.js +73 -0
- package/dist/core/orchestrator/chain-guard.d.ts +14 -0
- package/dist/core/orchestrator/chain-guard.js +23 -0
- package/dist/core/orchestrator/circuit-breaker.d.ts +65 -0
- package/dist/core/orchestrator/circuit-breaker.js +159 -0
- package/dist/core/orchestrator/claude-stream-parser.d.ts +31 -0
- package/dist/core/orchestrator/claude-stream-parser.js +99 -0
- package/dist/core/orchestrator/config-revisions.d.ts +6 -0
- package/dist/core/orchestrator/config-revisions.js +17 -0
- package/dist/core/orchestrator/dependency-resolver.d.ts +20 -0
- package/dist/core/orchestrator/dependency-resolver.js +78 -0
- package/dist/core/orchestrator/governance-gate.d.ts +110 -0
- package/dist/core/orchestrator/governance-gate.js +170 -0
- package/dist/core/orchestrator/learning-pipeline.d.ts +109 -0
- package/dist/core/orchestrator/learning-pipeline.js +249 -0
- package/dist/core/orchestrator/loop-detector.d.ts +51 -0
- package/dist/core/orchestrator/loop-detector.js +133 -0
- package/dist/core/orchestrator/ndjson-logger.d.ts +6 -0
- package/dist/core/orchestrator/ndjson-logger.js +18 -0
- package/dist/core/orchestrator/permission-relay.d.ts +72 -0
- package/dist/core/orchestrator/permission-relay.js +164 -0
- package/dist/core/orchestrator/run-manager.d.ts +31 -0
- package/dist/core/orchestrator/run-manager.js +178 -0
- package/dist/core/orchestrator/scheduler.d.ts +70 -0
- package/dist/core/orchestrator/scheduler.js +198 -0
- package/dist/core/orchestrator/secret-store.d.ts +57 -0
- package/dist/core/orchestrator/secret-store.js +171 -0
- package/dist/core/orchestrator/session-manager.d.ts +13 -0
- package/dist/core/orchestrator/session-manager.js +66 -0
- package/dist/core/orchestrator/task-queue.d.ts +34 -0
- package/dist/core/orchestrator/task-queue.js +83 -0
- package/dist/core/orchestrator/template-interpolate.d.ts +5 -0
- package/dist/core/orchestrator/template-interpolate.js +18 -0
- package/dist/core/orchestrator/user-registry.d.ts +47 -0
- package/dist/core/orchestrator/user-registry.js +76 -0
- package/dist/core/orchestrator/wakeup-queue.d.ts +9 -0
- package/dist/core/orchestrator/wakeup-queue.js +45 -0
- package/dist/core/orchestrator/workflow-engine.d.ts +47 -0
- package/dist/core/orchestrator/workflow-engine.js +204 -0
- package/dist/core/security/audit.d.ts +20 -0
- package/dist/core/security/audit.js +33 -0
- package/dist/core/security/column-validator.d.ts +20 -0
- package/dist/core/security/column-validator.js +37 -0
- package/dist/core/security/index.d.ts +5 -0
- package/dist/core/security/index.js +5 -0
- package/dist/core/security/process-env.d.ts +13 -0
- package/dist/core/security/process-env.js +49 -0
- package/dist/core/security/sanitizer.d.ts +11 -0
- package/dist/core/security/sanitizer.js +39 -0
- package/dist/core/security/types.d.ts +11 -0
- package/dist/core/security/types.js +1 -0
- package/dist/core/update/auto-update.d.ts +21 -0
- package/dist/core/update/auto-update.js +102 -0
- package/dist/core/update/backup-manager.d.ts +7 -0
- package/dist/core/update/backup-manager.js +24 -0
- package/dist/core/update/index.d.ts +8 -0
- package/dist/core/update/index.js +6 -0
- package/dist/core/update/migration-hooks.d.ts +11 -0
- package/dist/core/update/migration-hooks.js +10 -0
- package/dist/core/update/types.d.ts +11 -0
- package/dist/core/update/types.js +1 -0
- package/dist/core/update/update-checker.d.ts +11 -0
- package/dist/core/update/update-checker.js +63 -0
- package/dist/core/update/update-manager.d.ts +25 -0
- package/dist/core/update/update-manager.js +101 -0
- package/dist/core/update/version-utils.d.ts +6 -0
- package/dist/core/update/version-utils.js +34 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +20 -13
- package/dist/providers/anthropic/models.d.ts +2 -0
- package/dist/providers/anthropic/models.js +29 -0
- package/dist/providers/anthropic/provider.d.ts +13 -0
- package/dist/providers/anthropic/provider.js +119 -0
- package/dist/providers/anthropic/tool-converter.d.ts +10 -0
- package/dist/providers/anthropic/tool-converter.js +7 -0
- package/dist/providers/ollama/provider.d.ts +17 -0
- package/dist/providers/ollama/provider.js +185 -0
- package/dist/providers/openai/models.d.ts +2 -0
- package/dist/providers/openai/models.js +29 -0
- package/dist/providers/openai/provider.d.ts +13 -0
- package/dist/providers/openai/provider.js +163 -0
- package/dist/providers/openai/tool-converter.d.ts +10 -0
- package/dist/providers/openai/tool-converter.js +10 -0
- package/dist/shared/constants.d.ts +50 -0
- package/dist/shared/constants.js +64 -0
- package/dist/shared/index.d.ts +14 -0
- package/dist/shared/index.js +14 -0
- package/dist/shared/types/agent.d.ts +36 -0
- package/dist/shared/types/agent.js +2 -0
- package/dist/shared/types/channel.d.ts +70 -0
- package/dist/shared/types/channel.js +2 -0
- package/dist/shared/types/config.d.ts +111 -0
- package/dist/shared/types/config.js +2 -0
- package/dist/shared/types/connector.d.ts +77 -0
- package/dist/shared/types/connector.js +2 -0
- package/dist/shared/types/execution.d.ts +29 -0
- package/dist/shared/types/execution.js +2 -0
- package/dist/shared/types/provider.d.ts +73 -0
- package/dist/shared/types/provider.js +2 -0
- package/dist/shared/types/task.d.ts +47 -0
- package/dist/shared/types/task.js +2 -0
- package/dist/shared/types/workflow.d.ts +39 -0
- package/dist/shared/types/workflow.js +2 -0
- package/dist/shared/utils.d.ts +6 -0
- package/dist/shared/utils.js +13 -0
- package/dist/update-check.d.ts +5 -0
- package/dist/update-check.js +56 -0
- package/package.json +1 -1
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local voice transcription via whisper-node (whisper.cpp bindings).
|
|
3
|
+
*
|
|
4
|
+
* whisper-node is an optional dependency — if not installed, transcription
|
|
5
|
+
* degrades gracefully (returns null). Requires ffmpeg on the system PATH
|
|
6
|
+
* for audio format conversion.
|
|
7
|
+
*
|
|
8
|
+
* Setup:
|
|
9
|
+
* npm install whisper-node
|
|
10
|
+
* npx whisper-node download # download a model (e.g. base.en)
|
|
11
|
+
* brew install ffmpeg # or equivalent for your platform
|
|
12
|
+
*/
|
|
13
|
+
import { execFileSync } from "node:child_process";
|
|
14
|
+
import { writeFileSync, unlinkSync, mkdirSync } from "node:fs";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { randomUUID } from "node:crypto";
|
|
17
|
+
import os from "node:os";
|
|
18
|
+
import { createRequire } from "node:module";
|
|
19
|
+
const TEMP_DIR = join(os.tmpdir(), "botinabox-audio");
|
|
20
|
+
/**
|
|
21
|
+
* Transcribe an audio buffer using whisper-node.
|
|
22
|
+
*
|
|
23
|
+
* @param audioBuffer - Raw audio data (any format ffmpeg can decode)
|
|
24
|
+
* @param filename - Original filename (used for temp file extension)
|
|
25
|
+
* @param opts - Transcription options
|
|
26
|
+
* @returns Transcribed text, or null if transcription fails or whisper-node is not installed
|
|
27
|
+
*/
|
|
28
|
+
export async function transcribeAudio(audioBuffer, filename, opts) {
|
|
29
|
+
// Lazy-load whisper-node (optional CJS dependency)
|
|
30
|
+
let whisper;
|
|
31
|
+
try {
|
|
32
|
+
const require = createRequire(import.meta.url);
|
|
33
|
+
const mod = require("whisper-node");
|
|
34
|
+
whisper = mod.whisper ?? mod.default ?? mod;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
console.warn("[botinabox] whisper-node not installed — voice transcription unavailable. Run: npm install whisper-node && npx whisper-node download");
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
// Check ffmpeg availability
|
|
41
|
+
try {
|
|
42
|
+
execFileSync("ffmpeg", ["-version"], { stdio: "ignore" });
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
console.warn("[botinabox] ffmpeg not found — required for audio conversion. Install: brew install ffmpeg");
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const id = randomUUID().slice(0, 8);
|
|
49
|
+
const ext = filename.split(".").pop() ?? "aac";
|
|
50
|
+
mkdirSync(TEMP_DIR, { recursive: true });
|
|
51
|
+
const inputPath = join(TEMP_DIR, `${id}.${ext}`);
|
|
52
|
+
const wavPath = join(TEMP_DIR, `${id}.wav`);
|
|
53
|
+
try {
|
|
54
|
+
// Write audio to temp file
|
|
55
|
+
writeFileSync(inputPath, audioBuffer);
|
|
56
|
+
// Convert to WAV 16kHz mono PCM (required by whisper.cpp)
|
|
57
|
+
execFileSync("ffmpeg", ["-y", "-i", inputPath, "-ar", "16000", "-ac", "1", "-c:a", "pcm_s16le", wavPath], {
|
|
58
|
+
stdio: "ignore",
|
|
59
|
+
timeout: 30_000,
|
|
60
|
+
});
|
|
61
|
+
// Transcribe
|
|
62
|
+
const segments = await whisper(wavPath, {
|
|
63
|
+
modelName: opts?.modelName ?? "base.en",
|
|
64
|
+
whisperOptions: {
|
|
65
|
+
language: opts?.language ?? "auto",
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
if (!segments || segments.length === 0)
|
|
69
|
+
return null;
|
|
70
|
+
return segments.map((s) => s.speech).join(" ").trim();
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
console.error("[botinabox] Transcription failed:", err);
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
// Clean up temp files
|
|
78
|
+
try {
|
|
79
|
+
unlinkSync(inputPath);
|
|
80
|
+
}
|
|
81
|
+
catch { /* ignore */ }
|
|
82
|
+
try {
|
|
83
|
+
unlinkSync(wavPath);
|
|
84
|
+
}
|
|
85
|
+
catch { /* ignore */ }
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Download an audio file from a URL with bearer token authentication.
|
|
90
|
+
*/
|
|
91
|
+
export async function downloadAudio(url, token) {
|
|
92
|
+
try {
|
|
93
|
+
const resp = await fetch(url, {
|
|
94
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
95
|
+
});
|
|
96
|
+
if (!resp.ok) {
|
|
97
|
+
console.error(`[botinabox] Audio download failed: ${resp.status} ${resp.statusText}`);
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
return Buffer.from(await resp.arrayBuffer());
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
console.error("[botinabox] Audio download error:", err);
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebhookAdapter — ChannelAdapter implementation for webhook-based channels.
|
|
3
|
+
* Story 4.7
|
|
4
|
+
*/
|
|
5
|
+
import type { ChannelAdapter, ChannelCapabilities, ChannelMeta, ChannelConfig, InboundMessage, OutboundPayload, SendResult, HealthStatus } from "../../shared/index.js";
|
|
6
|
+
export declare class WebhookAdapter implements ChannelAdapter {
|
|
7
|
+
readonly id = "webhook";
|
|
8
|
+
readonly meta: ChannelMeta;
|
|
9
|
+
readonly capabilities: ChannelCapabilities;
|
|
10
|
+
onMessage?: (message: InboundMessage) => Promise<void>;
|
|
11
|
+
private connected;
|
|
12
|
+
private config;
|
|
13
|
+
private webhookServer;
|
|
14
|
+
connect(config: ChannelConfig): Promise<void>;
|
|
15
|
+
disconnect(): Promise<void>;
|
|
16
|
+
healthCheck(): Promise<HealthStatus>;
|
|
17
|
+
send(target: {
|
|
18
|
+
peerId: string;
|
|
19
|
+
threadId?: string;
|
|
20
|
+
}, payload: OutboundPayload): Promise<SendResult>;
|
|
21
|
+
}
|
|
22
|
+
/** Factory function — default export for auto-discovery. */
|
|
23
|
+
export default function createWebhookAdapter(): WebhookAdapter;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebhookAdapter — ChannelAdapter implementation for webhook-based channels.
|
|
3
|
+
* Story 4.7
|
|
4
|
+
*/
|
|
5
|
+
import { WebhookServer } from "./server.js";
|
|
6
|
+
export class WebhookAdapter {
|
|
7
|
+
id = "webhook";
|
|
8
|
+
meta = {
|
|
9
|
+
displayName: "Webhook",
|
|
10
|
+
homepage: "https://example.com",
|
|
11
|
+
};
|
|
12
|
+
capabilities = {
|
|
13
|
+
chatTypes: ["direct"],
|
|
14
|
+
threads: false,
|
|
15
|
+
reactions: false,
|
|
16
|
+
editing: false,
|
|
17
|
+
media: false,
|
|
18
|
+
polls: false,
|
|
19
|
+
maxTextLength: 65535,
|
|
20
|
+
formattingMode: "plain",
|
|
21
|
+
};
|
|
22
|
+
onMessage;
|
|
23
|
+
connected = false;
|
|
24
|
+
config = null;
|
|
25
|
+
webhookServer = null;
|
|
26
|
+
async connect(config) {
|
|
27
|
+
this.config = config;
|
|
28
|
+
this.connected = true;
|
|
29
|
+
// Start webhook HTTP server if port is specified
|
|
30
|
+
if (this.config.port) {
|
|
31
|
+
this.webhookServer = new WebhookServer({
|
|
32
|
+
port: this.config.port,
|
|
33
|
+
secret: this.config.secret,
|
|
34
|
+
onMessage: async (msg) => {
|
|
35
|
+
if (this.onMessage)
|
|
36
|
+
await this.onMessage(msg);
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
await this.webhookServer.start();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async disconnect() {
|
|
43
|
+
if (this.webhookServer) {
|
|
44
|
+
await this.webhookServer.stop();
|
|
45
|
+
this.webhookServer = null;
|
|
46
|
+
}
|
|
47
|
+
this.connected = false;
|
|
48
|
+
this.config = null;
|
|
49
|
+
}
|
|
50
|
+
async healthCheck() {
|
|
51
|
+
return { ok: this.connected };
|
|
52
|
+
}
|
|
53
|
+
async send(target, payload) {
|
|
54
|
+
if (!this.connected) {
|
|
55
|
+
return { success: false, error: "Not connected" };
|
|
56
|
+
}
|
|
57
|
+
const callbackUrl = this.config?.callbackUrl;
|
|
58
|
+
if (!callbackUrl) {
|
|
59
|
+
// No callback URL configured — accept silently (fire-and-forget style)
|
|
60
|
+
return { success: true };
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const body = JSON.stringify({
|
|
64
|
+
to: target.peerId,
|
|
65
|
+
threadId: target.threadId,
|
|
66
|
+
text: payload.text,
|
|
67
|
+
});
|
|
68
|
+
const response = await fetch(callbackUrl, {
|
|
69
|
+
method: "POST",
|
|
70
|
+
headers: { "Content-Type": "application/json" },
|
|
71
|
+
body,
|
|
72
|
+
});
|
|
73
|
+
if (response.ok) {
|
|
74
|
+
return { success: true };
|
|
75
|
+
}
|
|
76
|
+
return { success: false, error: `HTTP ${response.status}` };
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
return { success: false, error: String(err) };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** Factory function — default export for auto-discovery. */
|
|
84
|
+
export default function createWebhookAdapter() {
|
|
85
|
+
return new WebhookAdapter();
|
|
86
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HMAC signature verification for webhook payloads.
|
|
3
|
+
* Story 4.7
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Verify that the HMAC-SHA256 signature of body matches the provided signature.
|
|
7
|
+
* Uses timing-safe comparison to prevent timing attacks.
|
|
8
|
+
*
|
|
9
|
+
* @param body - The raw request body string
|
|
10
|
+
* @param secret - The shared HMAC secret
|
|
11
|
+
* @param signature - The signature to verify (hex string, possibly prefixed with "sha256=")
|
|
12
|
+
*/
|
|
13
|
+
export declare function verifyHmac(body: string, secret: string, signature: string): boolean;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HMAC signature verification for webhook payloads.
|
|
3
|
+
* Story 4.7
|
|
4
|
+
*/
|
|
5
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
6
|
+
/**
|
|
7
|
+
* Verify that the HMAC-SHA256 signature of body matches the provided signature.
|
|
8
|
+
* Uses timing-safe comparison to prevent timing attacks.
|
|
9
|
+
*
|
|
10
|
+
* @param body - The raw request body string
|
|
11
|
+
* @param secret - The shared HMAC secret
|
|
12
|
+
* @param signature - The signature to verify (hex string, possibly prefixed with "sha256=")
|
|
13
|
+
*/
|
|
14
|
+
export function verifyHmac(body, secret, signature) {
|
|
15
|
+
const expected = createHmac("sha256", secret).update(body, "utf8").digest("hex");
|
|
16
|
+
// Strip optional "sha256=" prefix
|
|
17
|
+
const provided = signature.startsWith("sha256=") ? signature.slice(7) : signature;
|
|
18
|
+
if (expected.length !== provided.length)
|
|
19
|
+
return false;
|
|
20
|
+
try {
|
|
21
|
+
return timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(provided, "hex"));
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal HTTP server for webhook inbound messages.
|
|
3
|
+
* Story 4.7
|
|
4
|
+
*/
|
|
5
|
+
import type { InboundMessage } from "../../shared/index.js";
|
|
6
|
+
export interface WebhookServerOpts {
|
|
7
|
+
port?: number;
|
|
8
|
+
secret?: string;
|
|
9
|
+
onMessage: (msg: InboundMessage) => Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
export declare class WebhookServer {
|
|
12
|
+
private server;
|
|
13
|
+
private readonly port;
|
|
14
|
+
private readonly secret;
|
|
15
|
+
private readonly onMessage;
|
|
16
|
+
constructor(opts: WebhookServerOpts);
|
|
17
|
+
start(): Promise<void>;
|
|
18
|
+
stop(): Promise<void>;
|
|
19
|
+
private handleRequest;
|
|
20
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal HTTP server for webhook inbound messages.
|
|
3
|
+
* Story 4.7
|
|
4
|
+
*/
|
|
5
|
+
import { createServer } from "node:http";
|
|
6
|
+
import { verifyHmac } from "./hmac.js";
|
|
7
|
+
export class WebhookServer {
|
|
8
|
+
server = null;
|
|
9
|
+
port;
|
|
10
|
+
secret;
|
|
11
|
+
onMessage;
|
|
12
|
+
constructor(opts) {
|
|
13
|
+
this.port = opts.port ?? 3200;
|
|
14
|
+
this.secret = opts.secret;
|
|
15
|
+
this.onMessage = opts.onMessage;
|
|
16
|
+
}
|
|
17
|
+
start() {
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
this.server = createServer((req, res) => {
|
|
20
|
+
void this.handleRequest(req, res);
|
|
21
|
+
});
|
|
22
|
+
this.server.listen(this.port, () => resolve());
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
stop() {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
if (!this.server) {
|
|
28
|
+
resolve();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
this.server.close((err) => {
|
|
32
|
+
if (err)
|
|
33
|
+
reject(err);
|
|
34
|
+
else
|
|
35
|
+
resolve();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
async handleRequest(req, res) {
|
|
40
|
+
const url = req.url ?? "/";
|
|
41
|
+
const method = req.method ?? "GET";
|
|
42
|
+
if (method !== "POST" || url !== "/webhook/inbound") {
|
|
43
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
44
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
// Read body
|
|
48
|
+
let body = "";
|
|
49
|
+
for await (const chunk of req) {
|
|
50
|
+
body += chunk;
|
|
51
|
+
}
|
|
52
|
+
// Verify HMAC if secret is configured
|
|
53
|
+
if (this.secret) {
|
|
54
|
+
const sig = req.headers["x-webhook-signature"];
|
|
55
|
+
if (!sig || !verifyHmac(body, this.secret, sig)) {
|
|
56
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
57
|
+
res.end(JSON.stringify({ error: "Invalid signature" }));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
let parsed;
|
|
62
|
+
try {
|
|
63
|
+
parsed = JSON.parse(body);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
67
|
+
res.end(JSON.stringify({ error: "Invalid JSON" }));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// Build InboundMessage from payload
|
|
71
|
+
const msg = {
|
|
72
|
+
id: parsed["id"] ?? `webhook-${Date.now()}`,
|
|
73
|
+
channel: "webhook",
|
|
74
|
+
from: parsed["from"] ?? "unknown",
|
|
75
|
+
body: parsed["text"] ?? "",
|
|
76
|
+
threadId: parsed["threadId"],
|
|
77
|
+
receivedAt: new Date().toISOString(),
|
|
78
|
+
raw: parsed,
|
|
79
|
+
};
|
|
80
|
+
try {
|
|
81
|
+
await this.onMessage(msg);
|
|
82
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
83
|
+
res.end(JSON.stringify({ ok: true }));
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
console.error('[webhook] Error:', err);
|
|
87
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
88
|
+
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export function configYmlTemplate(opts) {
|
|
2
|
+
const { name, channel = 'slack', provider = 'anthropic' } = opts;
|
|
3
|
+
const channelSection = channel === 'slack'
|
|
4
|
+
? ` slack:
|
|
5
|
+
enabled: true
|
|
6
|
+
token: \${SLACK_BOT_TOKEN}
|
|
7
|
+
appToken: \${SLACK_APP_TOKEN}
|
|
8
|
+
signingSecret: \${SLACK_SIGNING_SECRET}`
|
|
9
|
+
: channel === 'discord'
|
|
10
|
+
? ` discord:
|
|
11
|
+
enabled: true
|
|
12
|
+
token: \${DISCORD_BOT_TOKEN}`
|
|
13
|
+
: ` ${channel}:
|
|
14
|
+
enabled: true`;
|
|
15
|
+
const providerSection = provider === 'anthropic'
|
|
16
|
+
? ` anthropic:
|
|
17
|
+
enabled: true
|
|
18
|
+
apiKey: \${ANTHROPIC_API_KEY}`
|
|
19
|
+
: provider === 'openai'
|
|
20
|
+
? ` openai:
|
|
21
|
+
enabled: true
|
|
22
|
+
apiKey: \${OPENAI_API_KEY}`
|
|
23
|
+
: ` ${provider}:
|
|
24
|
+
enabled: true`;
|
|
25
|
+
return `name: ${name}
|
|
26
|
+
version: "1.0.0"
|
|
27
|
+
|
|
28
|
+
data:
|
|
29
|
+
path: ./data/bot.db
|
|
30
|
+
walMode: true
|
|
31
|
+
|
|
32
|
+
channels:
|
|
33
|
+
${channelSection}
|
|
34
|
+
|
|
35
|
+
providers:
|
|
36
|
+
${providerSection}
|
|
37
|
+
|
|
38
|
+
agents: []
|
|
39
|
+
|
|
40
|
+
models:
|
|
41
|
+
default: claude-3-5-sonnet-20241022
|
|
42
|
+
aliases: {}
|
|
43
|
+
routing: {}
|
|
44
|
+
fallbackChain: []
|
|
45
|
+
|
|
46
|
+
entities: {}
|
|
47
|
+
|
|
48
|
+
security: {}
|
|
49
|
+
|
|
50
|
+
render:
|
|
51
|
+
outputDir: ./generated
|
|
52
|
+
watchIntervalMs: 1000
|
|
53
|
+
|
|
54
|
+
updates:
|
|
55
|
+
policy: notify
|
|
56
|
+
checkIntervalMs: 3600000
|
|
57
|
+
|
|
58
|
+
budget:
|
|
59
|
+
warnPercent: 80
|
|
60
|
+
`;
|
|
61
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function envTemplate(channel?: string, provider?: string): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export function envTemplate(channel, provider) {
|
|
2
|
+
const lines = [
|
|
3
|
+
'# Bot environment variables',
|
|
4
|
+
'# Copy this file to .env and fill in the values',
|
|
5
|
+
'',
|
|
6
|
+
];
|
|
7
|
+
if (provider === 'anthropic' || !provider) {
|
|
8
|
+
lines.push('ANTHROPIC_API_KEY=your_anthropic_api_key_here');
|
|
9
|
+
}
|
|
10
|
+
else if (provider === 'openai') {
|
|
11
|
+
lines.push('OPENAI_API_KEY=your_openai_api_key_here');
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
lines.push(`${provider.toUpperCase()}_API_KEY=your_api_key_here`);
|
|
15
|
+
}
|
|
16
|
+
lines.push('');
|
|
17
|
+
if (channel === 'slack' || !channel) {
|
|
18
|
+
lines.push('SLACK_BOT_TOKEN=xoxb-your-slack-bot-token');
|
|
19
|
+
lines.push('SLACK_APP_TOKEN=xapp-your-slack-app-token');
|
|
20
|
+
lines.push('SLACK_SIGNING_SECRET=your_slack_signing_secret');
|
|
21
|
+
}
|
|
22
|
+
else if (channel === 'discord') {
|
|
23
|
+
lines.push('DISCORD_BOT_TOKEN=your_discord_bot_token');
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
lines.push(`${channel.toUpperCase()}_TOKEN=your_token_here`);
|
|
27
|
+
}
|
|
28
|
+
lines.push('');
|
|
29
|
+
return lines.join('\n');
|
|
30
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export function indexTsTemplate(name) {
|
|
2
|
+
return `import { DataStore } from 'botinabox';
|
|
3
|
+
import { readFileSync } from 'fs';
|
|
4
|
+
|
|
5
|
+
// Load config
|
|
6
|
+
const config = JSON.parse(readFileSync('./config.yml', 'utf-8'));
|
|
7
|
+
|
|
8
|
+
async function main(): Promise<void> {
|
|
9
|
+
console.log('Starting ${name}...');
|
|
10
|
+
// Initialize your bot here
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
main().catch(console.error);
|
|
14
|
+
`;
|
|
15
|
+
}
|
|
16
|
+
export function indexJsTemplate(name) {
|
|
17
|
+
return `import { DataStore } from 'botinabox';
|
|
18
|
+
import { readFileSync } from 'fs';
|
|
19
|
+
|
|
20
|
+
// Load config
|
|
21
|
+
const config = JSON.parse(readFileSync('./config.yml', 'utf-8'));
|
|
22
|
+
|
|
23
|
+
async function main() {
|
|
24
|
+
console.log('Starting ${name}...');
|
|
25
|
+
// Initialize your bot here
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
main().catch(console.error);
|
|
29
|
+
`;
|
|
30
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function packageJsonTemplate(opts) {
|
|
2
|
+
const { name, typescript = false } = opts;
|
|
3
|
+
const scripts = typescript
|
|
4
|
+
? {
|
|
5
|
+
build: 'tsc',
|
|
6
|
+
start: 'node dist/index.js',
|
|
7
|
+
dev: 'ts-node src/index.ts',
|
|
8
|
+
}
|
|
9
|
+
: {
|
|
10
|
+
start: 'node index.js',
|
|
11
|
+
};
|
|
12
|
+
const devDeps = typescript
|
|
13
|
+
? {
|
|
14
|
+
typescript: '^5.7.2',
|
|
15
|
+
'@types/node': '^22.10.0',
|
|
16
|
+
}
|
|
17
|
+
: {};
|
|
18
|
+
return JSON.stringify({
|
|
19
|
+
name,
|
|
20
|
+
version: '0.1.0',
|
|
21
|
+
type: 'module',
|
|
22
|
+
scripts,
|
|
23
|
+
dependencies: {
|
|
24
|
+
'botinabox': 'latest',
|
|
25
|
+
},
|
|
26
|
+
devDependencies: devDeps,
|
|
27
|
+
}, null, 2);
|
|
28
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Calendar connector — pulls calendar events.
|
|
3
|
+
*
|
|
4
|
+
* Produces `CalendarEventRecord` objects. Does NOT write to any database
|
|
5
|
+
* table; the consuming application decides how to store records.
|
|
6
|
+
*/
|
|
7
|
+
import type { Connector, ConnectorMeta, AuthResult, SyncOptions, SyncResult } from '../../shared/types/connector.js';
|
|
8
|
+
import type { CalendarEventRecord, GoogleConnectorConfig } from './types.js';
|
|
9
|
+
export interface CalendarConnectorOpts {
|
|
10
|
+
/** Load persisted tokens for a given account key (OAuth2 flow only). */
|
|
11
|
+
tokenLoader?: (key: string) => Promise<string | null>;
|
|
12
|
+
/** Persist tokens for a given account key (OAuth2 flow only). */
|
|
13
|
+
tokenSaver?: (key: string, value: string) => Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
export declare class GoogleCalendarConnector implements Connector<CalendarEventRecord> {
|
|
16
|
+
readonly id = "google-calendar";
|
|
17
|
+
readonly meta: ConnectorMeta;
|
|
18
|
+
private tokenLoader?;
|
|
19
|
+
private tokenSaver?;
|
|
20
|
+
private client;
|
|
21
|
+
private config;
|
|
22
|
+
private tokens;
|
|
23
|
+
private calendar;
|
|
24
|
+
constructor(opts?: CalendarConnectorOpts);
|
|
25
|
+
connect(config: GoogleConnectorConfig): Promise<void>;
|
|
26
|
+
disconnect(): Promise<void>;
|
|
27
|
+
healthCheck(): Promise<{
|
|
28
|
+
ok: boolean;
|
|
29
|
+
account?: string;
|
|
30
|
+
error?: string;
|
|
31
|
+
}>;
|
|
32
|
+
authenticate(codeProvider: (authUrl: string) => Promise<string>): Promise<AuthResult>;
|
|
33
|
+
sync(options?: SyncOptions): Promise<SyncResult<CalendarEventRecord>>;
|
|
34
|
+
/** Incremental sync using Calendar syncToken. */
|
|
35
|
+
private syncIncremental;
|
|
36
|
+
/** Full sync using timeMin. */
|
|
37
|
+
private syncFull;
|
|
38
|
+
private ensureConnected;
|
|
39
|
+
private mapEvent;
|
|
40
|
+
}
|