Commit 81b0453c2657
2026-02-16 14:43:13
1 parent
afe932f
Changed files (4)
src/main-pi.ts
@@ -0,0 +1,159 @@
+#!/usr/bin/env node
+/**
+ * Daneel - XMPP Research Bot (Pi Edition)
+ * Main entry point using Pi libraries
+ */
+
+import { XmppClient } from "./xmpp/client.js";
+import { XmppAgent } from "./pi/agent-wrapper.js";
+import { getDefaultModel } from "./pi/config.js";
+import { bareJid } from "./xmpp/types.js";
+import { statusTool } from "./pi/tools/status.js";
+import * as os from "os";
+
+interface Config {
+ xmpp: {
+ jid: string;
+ password: string;
+ ownerJid: string;
+ };
+ paths: {
+ dataDir: string;
+ inboxPath: string;
+ };
+ debug: boolean;
+}
+
+function loadConfig(): Config {
+ const jid = process.env.DANEEL_XMPP_JID;
+ const password = process.env.DANEEL_XMPP_PASSWORD;
+ const ownerJid = process.env.DANEEL_OWNER_JID;
+
+ if (!jid || !password || !ownerJid) {
+ console.error("Missing required environment variables:");
+ if (!jid) console.error(" DANEEL_XMPP_JID");
+ if (!password) console.error(" DANEEL_XMPP_PASSWORD");
+ if (!ownerJid) console.error(" DANEEL_OWNER_JID");
+ process.exit(1);
+ }
+
+ const dataDir = process.env.DANEEL_DATA_DIR || "./data";
+ const inboxPath = process.env.DANEEL_INBOX_PATH ||
+ `${os.homedir()}/desktop/org/inbox.org`;
+ const debug = process.env.DANEEL_DEBUG === "true";
+
+ return {
+ xmpp: { jid, password, ownerJid },
+ paths: { dataDir, inboxPath },
+ debug,
+ };
+}
+
+async function main() {
+ console.log("Daneel - XMPP Research Bot");
+ console.log("===========================\n");
+
+ const config = loadConfig();
+
+ // Get default model
+ const defaultModel = getDefaultModel();
+ console.log(`Default model: ${defaultModel.provider}/${defaultModel.id}`);
+
+ // Available tools
+ const tools = [statusTool];
+ console.log(`Tools available: ${tools.map(t => t.name).join(", ")}`);
+
+ // Map of JID -> XmppAgent
+ const agents = new Map<string, XmppAgent>();
+
+ function getOrCreateAgent(jid: string): XmppAgent {
+ const bare = bareJid(jid);
+ let agent = agents.get(bare);
+
+ if (!agent) {
+ if (config.debug) {
+ console.log(`[DEBUG] Creating new agent for JID: ${bare}`);
+ }
+ agent = new XmppAgent(bare, defaultModel, tools);
+ agents.set(bare, agent);
+ }
+
+ return agent;
+ }
+
+ // Create XMPP client
+ const xmpp = new XmppClient({
+ xmpp: config.xmpp,
+ llm: {
+ defaultModel: { provider: defaultModel.provider as any, model: defaultModel.id },
+ providers: {},
+ },
+ paths: config.paths,
+ debug: config.debug,
+ });
+
+ // Set up message handler
+ xmpp.onMessage(async (message) => {
+ const timestamp = new Date().toISOString();
+ const from = bareJid(message.from);
+ const preview = message.body.slice(0, 50);
+
+ console.log(`[${timestamp}] Message from ${from}: ${preview}${message.body.length > 50 ? "..." : ""}`);
+
+ try {
+ const agent = getOrCreateAgent(message.from);
+ const response = await agent.processMessage(message.body);
+
+ await xmpp.sendMessage(message.from, response);
+
+ if (config.debug) {
+ console.log(`[${timestamp}] Response sent (${response.length} chars)`);
+ } else {
+ console.log(`[${timestamp}] Response sent`);
+ }
+ } catch (error) {
+ console.error(`[${timestamp}] Error processing message:`, error);
+
+ const errorMsg = error instanceof Error ? error.message : "Unknown error";
+ await xmpp.sendMessage(
+ message.from,
+ `Error processing your message: ${errorMsg}`
+ );
+ }
+ });
+
+ // Graceful shutdown
+ const shutdown = async (signal: string) => {
+ console.log(`\nReceived ${signal}, shutting down gracefully...`);
+
+ // TODO: Save sessions to disk (Phase 9)
+
+ await xmpp.stop();
+ console.log("XMPP client stopped");
+
+ process.exit(0);
+ };
+
+ process.on("SIGINT", () => shutdown("SIGINT"));
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
+
+ // Start XMPP client
+ console.log("\nConfiguration:");
+ console.log(` Bot JID: ${config.xmpp.jid}`);
+ console.log(` Owner JID: ${config.xmpp.ownerJid}`);
+ console.log(` Data dir: ${config.paths.dataDir}`);
+ console.log(` Inbox path: ${config.paths.inboxPath}`);
+ console.log(` Debug mode: ${config.debug}`);
+ console.log();
+ console.log("Connecting to XMPP server...");
+
+ await xmpp.start();
+
+ console.log("Connected! Waiting for messages...\n");
+}
+
+// Run main function
+main().catch((error) => {
+ console.error("Fatal error:", error);
+ process.exit(1);
+});
src/main.test.ts
@@ -0,0 +1,112 @@
+/**
+ * Tests for Main Entry Point
+ *
+ * These are integration tests that verify the main application
+ * wiring and configuration loading.
+ */
+
+import { describe, it, expect, beforeEach, afterEach } from "vitest";
+import * as os from "os";
+
+describe("Main Application Configuration", () => {
+ const originalEnv = process.env;
+
+ beforeEach(() => {
+ // Reset environment for each test
+ process.env = { ...originalEnv };
+ });
+
+ afterEach(() => {
+ process.env = originalEnv;
+ });
+
+ describe("Environment Variable Validation", () => {
+ it("should require DANEEL_XMPP_JID", () => {
+ delete process.env.DANEEL_XMPP_JID;
+ process.env.DANEEL_XMPP_PASSWORD = "test";
+ process.env.DANEEL_OWNER_JID = "test@xmpp.sbr.pm";
+
+ // Config loading should fail without JID
+ expect(() => {
+ // We'll implement loadConfig() function
+ const jid = process.env.DANEEL_XMPP_JID;
+ if (!jid) throw new Error("DANEEL_XMPP_JID required");
+ }).toThrow();
+ });
+
+ it("should require DANEEL_XMPP_PASSWORD", () => {
+ process.env.DANEEL_XMPP_JID = "bot@xmpp.sbr.pm";
+ delete process.env.DANEEL_XMPP_PASSWORD;
+ process.env.DANEEL_OWNER_JID = "test@xmpp.sbr.pm";
+
+ expect(() => {
+ const password = process.env.DANEEL_XMPP_PASSWORD;
+ if (!password) throw new Error("DANEEL_XMPP_PASSWORD required");
+ }).toThrow();
+ });
+
+ it("should require DANEEL_OWNER_JID", () => {
+ process.env.DANEEL_XMPP_JID = "bot@xmpp.sbr.pm";
+ process.env.DANEEL_XMPP_PASSWORD = "test";
+ delete process.env.DANEEL_OWNER_JID;
+
+ expect(() => {
+ const ownerJid = process.env.DANEEL_OWNER_JID;
+ if (!ownerJid) throw new Error("DANEEL_OWNER_JID required");
+ }).toThrow();
+ });
+
+ it("should accept valid configuration", () => {
+ process.env.DANEEL_XMPP_JID = "bot@xmpp.sbr.pm";
+ process.env.DANEEL_XMPP_PASSWORD = "test";
+ process.env.DANEEL_OWNER_JID = "owner@xmpp.sbr.pm";
+
+ expect(() => {
+ const jid = process.env.DANEEL_XMPP_JID;
+ const password = process.env.DANEEL_XMPP_PASSWORD;
+ const ownerJid = process.env.DANEEL_OWNER_JID;
+ if (!jid || !password || !ownerJid) throw new Error("Config incomplete");
+ }).not.toThrow();
+ });
+ });
+
+ describe("Optional Configuration", () => {
+ it("should use default data directory if not specified", () => {
+ delete process.env.DANEEL_DATA_DIR;
+ const dataDir = process.env.DANEEL_DATA_DIR || "./data";
+ expect(dataDir).toBe("./data");
+ });
+
+ it("should use custom data directory if specified", () => {
+ process.env.DANEEL_DATA_DIR = "/custom/path";
+ const dataDir = process.env.DANEEL_DATA_DIR || "./data";
+ expect(dataDir).toBe("/custom/path");
+ });
+
+ it("should use default inbox path if not specified", () => {
+ delete process.env.DANEEL_INBOX_PATH;
+ const inboxPath = process.env.DANEEL_INBOX_PATH ||
+ `${os.homedir()}/desktop/org/inbox.org`;
+ expect(inboxPath).toContain("inbox.org");
+ });
+
+ it("should use custom inbox path if specified", () => {
+ process.env.DANEEL_INBOX_PATH = "/custom/inbox.org";
+ const inboxPath = process.env.DANEEL_INBOX_PATH ||
+ `${os.homedir()}/desktop/org/inbox.org`;
+ expect(inboxPath).toBe("/custom/inbox.org");
+ });
+
+ it("should default debug to false", () => {
+ delete process.env.DANEEL_DEBUG;
+ const debug = process.env.DANEEL_DEBUG === "true";
+ expect(debug).toBe(false);
+ });
+
+ it("should enable debug when set to true", () => {
+ process.env.DANEEL_DEBUG = "true";
+ const debug = process.env.DANEEL_DEBUG === "true";
+ expect(debug).toBe(true);
+ });
+ });
+});
package.json
@@ -7,8 +7,9 @@
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
- "start": "node dist/main.js",
- "start:pi": "node dist/pi/main-test.js",
+ "start": "node dist/main-pi.js",
+ "start:old": "node dist/main.js",
+ "start:test": "node dist/pi/main-test.js",
"test": "vitest run",
"test:watch": "vitest",
"test:ui": "vitest --ui"
test-pi-bot.sh
@@ -77,5 +77,5 @@ export DANEEL_INBOX_PATH
export DANEEL_DEBUG
export GOOGLE_API_KEY
-# Run the Pi-based bot
-exec npm run start:pi
+# Run the Pi-based bot (now the default)
+exec npm start