diff --git a/apps/x/DOCKER.md b/apps/x/DOCKER.md new file mode 100644 index 00000000..fcc671e8 --- /dev/null +++ b/apps/x/DOCKER.md @@ -0,0 +1,36 @@ +# Running Rowboat (Headless) in Docker + +You can run the core agent logic of Rowboat in a Docker container, suitable for server environments or always-on agents. + +## Build + +From the `apps/x` directory: + +```bash +docker build -t rowboat-agent . +``` + +## Run + +You need to mount a volume for the data directory (`~/.rowboat`) to persist your knowledge graph and credentials. + +```bash +docker run -d \ + --name rowboat \ + -v $(pwd)/rowboat-data:/data/.rowboat \ + rowboat-agent +``` + +## Configuration + +The agent uses the configuration files in your data volume (`/data/.rowboat/config/`). +If you are starting fresh, you may need to manually populate `models.json` or `config.json` in that volume, as there is no UI to guide you through onboarding in this headless mode. + +### Environment Variables + +You can inject API keys via environment variables which Rowboat will pick up (if configured to read them): + +- `OPENAI_API_KEY` +- `ANTHROPIC_API_KEY` +- `GOOGLE_API_KEY` +``` diff --git a/apps/x/Dockerfile b/apps/x/Dockerfile new file mode 100644 index 00000000..2aac4a78 --- /dev/null +++ b/apps/x/Dockerfile @@ -0,0 +1,52 @@ +FROM node:22-slim + +# Install system dependencies if needed (e.g. for native modules) +RUN apt-get update && apt-get install -y \ + python3 \ + make \ + g++ \ + && rm -rf /var/lib/apt/lists/* + +# Enable pnpm +RUN corepack enable + +WORKDIR /app + +# Copy workspace config +COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./ + +# Copy packages +COPY apps/server ./apps/server +COPY packages/core ./packages/core +COPY packages/shared ./packages/shared +# Note: we skip apps/main, apps/renderer, apps/preload as they are Electron-specific + +# Install dependencies (skipping Electron devDeps if possible, but they are in root) +# We might need to ignore scripts or optional deps +RUN pnpm install --frozen-lockfile + +# Build dependencies in order +WORKDIR /app/packages/shared +RUN npm run build + +WORKDIR /app/packages/core +RUN npm run build + +WORKDIR /app/apps/server +RUN npm run build + +WORKDIR /app + +# Set environment to production +ENV NODE_ENV=production + +# The app uses ~/.rowboat for storage by default. +# In Docker, we should probably mount a volume or set HOME. +# Let's verify if we need to override the path via env var, but the code hardcodes path.join(homedir(), ".rowboat") +# So setting HOME=/data should work, provided we create the dir. +ENV HOME=/data +RUN mkdir -p /data/.rowboat + +VOLUME /data/.rowboat + +CMD ["node", "apps/server/dist/main.js"] diff --git a/apps/x/apps/server/package.json b/apps/x/apps/server/package.json new file mode 100644 index 00000000..e135098b --- /dev/null +++ b/apps/x/apps/server/package.json @@ -0,0 +1,21 @@ +{ + "name": "@x/server", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "dist/main.js", + "scripts": { + "build": "tsc", + "start": "node dist/main.js", + "dev": "tsx src/main.ts" + }, + "dependencies": { + "@x/core": "workspace:*", + "@x/shared": "workspace:*" + }, + "devDependencies": { + "tsx": "^4.19.1", + "typescript": "^5.6.3", + "@types/node": "^22.7.5" + } +} diff --git a/apps/x/apps/server/src/main.ts b/apps/x/apps/server/src/main.ts new file mode 100644 index 00000000..b0028cb6 --- /dev/null +++ b/apps/x/apps/server/src/main.ts @@ -0,0 +1,67 @@ +import { init as initGmailSync } from "@x/core/dist/knowledge/sync_gmail.js"; +import { init as initCalendarSync } from "@x/core/dist/knowledge/sync_calendar.js"; +import { init as initFirefliesSync } from "@x/core/dist/knowledge/sync_fireflies.js"; +import { init as initGranolaSync } from "@x/core/dist/knowledge/granola/sync.js"; +import { init as initGraphBuilder } from "@x/core/dist/knowledge/build_graph.js"; +import { init as initPreBuiltRunner } from "@x/core/dist/pre_built/runner.js"; +import { init as initAgentRunner } from "@x/core/dist/agent-schedule/runner.js"; +import { initConfigs } from "@x/core/dist/config/initConfigs.js"; +import { startWorkspaceWatcher } from "@x/core/dist/workspace/watcher.js"; + +async function main() { + console.log("Rowboat Headless Server starting..."); + + // Initialize all config files + await initConfigs(); + console.log("Configs initialized."); + + // Start workspace watcher (optional in headless, but good for reactivity if files change) + // We need to import it from core if possible, or reimplement the watcher start logic if it was only in main.ts + // Looking at main.ts imports: import { startWorkspaceWatcher } from "./ipc.js"; + // Wait, startWorkspaceWatcher in main.ts came from "./ipc.js". I need to find the real core watcher. + // I saw "rowboat-workspace/apps/x/packages/core/src/workspace/watcher.ts" in grep results. + // So I can import it directly. + + // Note: The main.ts version wrapped it. I will try to use the core one directly if available. + // Let's assume the side-effects are safe. + + // Start services + console.log("Starting Gmail sync..."); + initGmailSync(); + + console.log("Starting Calendar sync..."); + initCalendarSync(); + + console.log("Starting Fireflies sync..."); + initFirefliesSync(); + + console.log("Starting Granola sync..."); + initGranolaSync(); + + console.log("Starting Graph Builder..."); + initGraphBuilder(); + + console.log("Starting Pre-built Runner..."); + initPreBuiltRunner(); + + console.log("Starting Agent Runner..."); + initAgentRunner(); + + console.log("Rowboat Headless Server is running. Press Ctrl+C to stop."); + + // Keep process alive + process.stdin.resume(); + + const cleanup = () => { + console.log("Stopping Rowboat Headless Server..."); + process.exit(0); + }; + + process.on("SIGINT", cleanup); + process.on("SIGTERM", cleanup); +} + +main().catch((err) => { + console.error("Fatal error:", err); + process.exit(1); +}); diff --git a/apps/x/apps/server/tsconfig.json b/apps/x/apps/server/tsconfig.json new file mode 100644 index 00000000..c3cffcf3 --- /dev/null +++ b/apps/x/apps/server/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "target": "ES2022" + }, + "include": ["src/**/*"] +}