diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..47208f2 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +node_modules +dist +.env +workspace +docs +.git diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e5a23a4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +FROM node:24-bookworm-slim + +RUN apt-get update \ + && apt-get install -y --no-install-recommends bash ca-certificates git ripgrep python3 python-is-python3 \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm ci --ignore-scripts +COPY tsconfig.json tsconfig.build.json ./ +COPY src ./src +RUN npm run build && npm prune --omit=dev + +ENV NODE_ENV=production \ + WORKSPACE_DIR=/workspace \ + PI_CODING_AGENT_DIR=/pi-agent +VOLUME ["/workspace", "/pi-agent"] + +CMD ["node", "dist/main.js"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..af41e92 --- /dev/null +++ b/README.md @@ -0,0 +1,90 @@ +# Volcanic Secretary + +A personal assistant you talk to over Telegram. The agent engine is [Pi](https://pi.dev) embedded through its SDK. It lives in one workspace directory, runs tools without asking, remembers things in `MEMORY.md`, and can schedule reminders, prompts, and watcher scripts. + +## Setup (Windows or any host with Node 24) + +1. Create a bot with @BotFather and copy the token. +2. Find your numeric Telegram user id (send `/start` to @userinfobot). +3. Log in to a model provider once with `pi` so `~/.pi/agent/auth.json` exists, or plan to set `PI_MODEL` plus a provider API key. +4. Copy `.env.example` to `.env` and fill in `TELEGRAM_BOT_TOKEN` and `TELEGRAM_OWNER_ID`. Add `OPENAI_API_KEY` if you want voice notes transcribed. +5. `npm install`, then `npm run dev` (development) or `npm run build && npm start`. + +The bot messages you `🟒 Online.` when it is ready. Only your user id is accepted; everyone else is ignored. + +## Talking to it + +Send text, photos, voice notes, or files. Photos go to the model as images. Voice notes are transcribed with OpenAI and echoed back as `🎀 ...`. Files are saved to `workspace/inbox/` and the agent is told the path. + +While the agent works you see one `βš™οΈ Working…` message that updates with tool activity, then the answer. Send another message while it works and it is queued for the current run (you get a πŸ‘€ reaction). + +Commands: `/status`, `/new`, `/stop`, `/jobs`, `/cancel `, `/compact`. + +## Scheduling + +Ask in plain language: + +- "Remind me at 9 tomorrow to call the dentist" (one-off, sent directly) +- "Every weekday at 8, summarize what's in my inbox folder" (recurring prompt through the agent) +- "Write a script that checks the price on this page every 10 minutes and tell me only if it drops below 50" (watcher script) + +Watcher scripts follow one rule: print nothing and exit 0 to stay silent, print a message to trigger delivery, exit non-zero on a real error. Failures are reported once and stay quiet while the same failure repeats. + +Jobs live in `workspace/.secretary/jobs.json`. + +## Workspace + +``` +workspace/ + AGENTS.md persona and standing instructions (edit freely) + MEMORY.md facts the assistant keeps; it edits this itself + scripts/ scripts for scheduled jobs + inbox/ files you sent + .secretary/ runtime state +``` + +The agent's edits to `MEMORY.md` are local runtime data. Leave them uncommitted, or run `git update-index --assume-unchanged workspace/MEMORY.md`. + +## Environment variables + +| Variable | Required | Default | Purpose | +| --- | --- | --- | --- | +| `TELEGRAM_BOT_TOKEN` | yes | | Bot token | +| `TELEGRAM_OWNER_ID` | yes | | Your numeric user id | +| `WORKSPACE_DIR` | no | `./workspace` | Workspace directory | +| `OPENAI_API_KEY` | no | | Enables voice transcription | +| `STT_MODEL` | no | `gpt-4o-transcribe` | OpenAI transcription model | +| `PI_MODEL` | no | Pi default | `provider/model` or `provider/model:thinking` | +| `PI_CODING_AGENT_DIR` | no | `~/.pi/agent` | Pi config directory (auth, settings, packages) | +| `SECRETARY_TIMEZONE` | no | system | IANA timezone for cron and time display | +| `SECRETARY_SHELL` | no | Git Bash / bash | Shell used for scheduled scripts | +| `LOG_LEVEL` | no | `info` | `debug`, `info`, `warn`, `error` | + +## Keeping it running on Windows + +Use Task Scheduler (run `node dist/main.js` at logon, restart on failure) or `npm i -g pm2` then `pm2 start dist/main.js --name secretary`. + +## Docker / Coolify + +`docker compose up -d --build` runs it with two named volumes: `/workspace` and `/pi-agent`. Provide the environment through Coolify. For model auth either copy a Pi agent directory containing `auth.json` into the `/pi-agent` volume, or set `ANTHROPIC_API_KEY` and `PI_MODEL=anthropic/claude-opus-4-8` (or another model you have access to). The image includes bash, git, ripgrep, and Python 3 so watcher scripts work the same as on Windows. + +## Tests + +`npm test` runs the unit tests. `SECRETARY_SMOKE=1 npx vitest run src/agent/pi-backend.smoke.test.ts` runs one real model call through Pi. + +### Manual end-to-end checklist + +Run `npm run dev` with a real bot and walk through every line: + +1. `hi` β†’ progress message appears then disappears, reply arrives as formatted text. +2. Long answer (`explain git rebase in detail with code examples`) β†’ multiple messages, code blocks render, no HTML shows raw. +3. Photo with caption `what is this` β†’ reply describes the image. +4. Voice note β†’ `🎀 ` then a reply. Without `OPENAI_API_KEY` β†’ the not-configured note. +5. Document (a small `.csv`) with caption `summarize this` β†’ file appears in `workspace/inbox/` with date prefix, reply uses it. +6. Send a message while a long run is in progress β†’ πŸ‘€ reaction, and the agent picks it up in the same run. +7. `/stop` during a run β†’ `Stopped.` and no final answer message. +8. `remind me in 2 minutes to stretch` β†’ confirmation with the fire time; two minutes later `: ` arrives directly. +9. `every minute run scripts/ping.py and tell me if it prints something` where `ping.py` prints on even minutes β†’ messages arrive only on even minutes; `/jobs` lists it; `/cancel ` removes it. +10. Ask it to remember a fact (`remember that my dog is called Rex`), then `/new`, then `what is my dog called` β†’ answer comes from `MEMORY.md`. +11. Stop the daemon, start it again β†’ conversation continues (`what did we just talk about`). +12. `/compact` β†’ reply with token counts. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..953e41c --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,20 @@ +services: + secretary: + build: . + restart: unless-stopped + environment: + - TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN} + - TELEGRAM_OWNER_ID=${TELEGRAM_OWNER_ID} + - OPENAI_API_KEY=${OPENAI_API_KEY:-} + - STT_MODEL=${STT_MODEL:-gpt-4o-transcribe} + - PI_MODEL=${PI_MODEL:-} + - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-} + - SECRETARY_TIMEZONE=${SECRETARY_TIMEZONE:-UTC} + - LOG_LEVEL=${LOG_LEVEL:-info} + volumes: + - secretary-workspace:/workspace + - secretary-pi-agent:/pi-agent + +volumes: + secretary-workspace: + secretary-pi-agent: diff --git a/workspace/AGENTS.md b/workspace/AGENTS.md new file mode 100644 index 0000000..ac59032 --- /dev/null +++ b/workspace/AGENTS.md @@ -0,0 +1,10 @@ +# Workspace + +This directory belongs to Volcanic Secretary, a personal assistant reached over Telegram. + +- MEMORY.md: durable facts about the owner. Keep it current. +- scripts/: scripts for scheduled jobs. +- inbox/: files the owner sent. +- .secretary/: runtime state. Do not edit by hand. + +Standing instructions for the assistant go below this line. diff --git a/workspace/MEMORY.md b/workspace/MEMORY.md new file mode 100644 index 0000000..b4b74fb --- /dev/null +++ b/workspace/MEMORY.md @@ -0,0 +1,9 @@ +# Memory + +Durable facts the assistant should remember. The assistant edits this file itself. + +## Owner + +## Preferences + +## Ongoing diff --git a/workspace/inbox/.gitkeep b/workspace/inbox/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/workspace/scripts/.gitkeep b/workspace/scripts/.gitkeep new file mode 100644 index 0000000..e69de29