Add README, Docker packaging, and workspace templates
Docker build verified locally (docker build -t volcanic-secretary . succeeded on this machine). Step 6's manual end-to-end checklist against a real Telegram bot was not run in this environment (no bot token or owner id available); the checklist is kept in the README's Tests section for the owner to run before relying on this in production. Claude-Session: https://claude.ai/code/session_01JtuZTCMW9TyogEmBgGf8JT
This commit is contained in:
6
.dockerignore
Normal file
6
.dockerignore
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
.env
|
||||||
|
workspace
|
||||||
|
docs
|
||||||
|
.git
|
||||||
19
Dockerfile
Normal file
19
Dockerfile
Normal file
@@ -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"]
|
||||||
90
README.md
Normal file
90
README.md
Normal file
@@ -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 <id>`, `/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 → `🎤 <transcript>` 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 `<job name>: <message>` 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 <id>` 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.
|
||||||
20
docker-compose.yml
Normal file
20
docker-compose.yml
Normal file
@@ -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:
|
||||||
10
workspace/AGENTS.md
Normal file
10
workspace/AGENTS.md
Normal file
@@ -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.
|
||||||
9
workspace/MEMORY.md
Normal file
9
workspace/MEMORY.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Memory
|
||||||
|
|
||||||
|
Durable facts the assistant should remember. The assistant edits this file itself.
|
||||||
|
|
||||||
|
## Owner
|
||||||
|
|
||||||
|
## Preferences
|
||||||
|
|
||||||
|
## Ongoing
|
||||||
0
workspace/inbox/.gitkeep
Normal file
0
workspace/inbox/.gitkeep
Normal file
0
workspace/scripts/.gitkeep
Normal file
0
workspace/scripts/.gitkeep
Normal file
Reference in New Issue
Block a user