@lovenyberg/ove 0.1.0

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.
Files changed (63) hide show
  1. package/.dockerignore +7 -0
  2. package/.env.example +30 -0
  3. package/.github/workflows/ci.yml +16 -0
  4. package/.github/workflows/pages.yml +33 -0
  5. package/.github/workflows/publish.yml +45 -0
  6. package/CLAUDE.md +28 -0
  7. package/Dockerfile +37 -0
  8. package/LICENSE +21 -0
  9. package/README.md +183 -0
  10. package/bin/ove.ts +72 -0
  11. package/bun.lock +703 -0
  12. package/bunfig.toml +2 -0
  13. package/config.example.json +35 -0
  14. package/deploy/ove.service +15 -0
  15. package/docker-compose.yml +15 -0
  16. package/docs/CNAME +1 -0
  17. package/docs/examples.md +198 -0
  18. package/docs/index.html +922 -0
  19. package/docs/logo.png +0 -0
  20. package/logo.png +0 -0
  21. package/package.json +44 -0
  22. package/public/index.html +83 -0
  23. package/src/adapters/cli.ts +93 -0
  24. package/src/adapters/discord.test.ts +13 -0
  25. package/src/adapters/discord.ts +98 -0
  26. package/src/adapters/github.test.ts +21 -0
  27. package/src/adapters/github.ts +134 -0
  28. package/src/adapters/http.test.ts +87 -0
  29. package/src/adapters/http.ts +167 -0
  30. package/src/adapters/slack.ts +123 -0
  31. package/src/adapters/telegram.test.ts +15 -0
  32. package/src/adapters/telegram.ts +75 -0
  33. package/src/adapters/types.test.ts +38 -0
  34. package/src/adapters/types.ts +32 -0
  35. package/src/adapters/whatsapp.ts +118 -0
  36. package/src/adapters/wiring.test.ts +16 -0
  37. package/src/config.test.ts +51 -0
  38. package/src/config.ts +73 -0
  39. package/src/cron.test.ts +65 -0
  40. package/src/cron.ts +80 -0
  41. package/src/flows.test.ts +312 -0
  42. package/src/index.ts +588 -0
  43. package/src/logger.ts +29 -0
  44. package/src/queue.test.ts +113 -0
  45. package/src/queue.ts +131 -0
  46. package/src/repos.test.ts +31 -0
  47. package/src/repos.ts +81 -0
  48. package/src/router.test.ts +167 -0
  49. package/src/router.ts +125 -0
  50. package/src/runner.ts +21 -0
  51. package/src/runners/claude.test.ts +77 -0
  52. package/src/runners/claude.ts +107 -0
  53. package/src/schedule-flow.test.ts +67 -0
  54. package/src/schedule-parser.test.ts +37 -0
  55. package/src/schedule-parser.ts +70 -0
  56. package/src/schedules.test.ts +54 -0
  57. package/src/schedules.ts +73 -0
  58. package/src/sessions.test.ts +45 -0
  59. package/src/sessions.ts +55 -0
  60. package/src/setup.test.ts +195 -0
  61. package/src/setup.ts +248 -0
  62. package/src/smoke.test.ts +80 -0
  63. package/tsconfig.json +14 -0
package/.dockerignore ADDED
@@ -0,0 +1,7 @@
1
+ node_modules/
2
+ repos/
3
+ .env
4
+ ove.db
5
+ .git/
6
+ docs/
7
+ *.md
package/.env.example ADDED
@@ -0,0 +1,30 @@
1
+ # Slack (Socket Mode)
2
+ SLACK_BOT_TOKEN=xoxb-...
3
+ SLACK_APP_TOKEN=xapp-...
4
+
5
+ # WhatsApp
6
+ WHATSAPP_ENABLED=false
7
+
8
+ # Telegram
9
+ # TELEGRAM_BOT_TOKEN=123456:ABC-DEF...
10
+
11
+ # Discord
12
+ # DISCORD_BOT_TOKEN=MTIz...
13
+
14
+ # HTTP API + Web UI
15
+ # HTTP_API_PORT=3000
16
+ # HTTP_API_KEY=your-secret-key
17
+
18
+ # GitHub (polling for @mentions in issue/PR comments)
19
+ # GITHUB_POLL_REPOS=owner/repo1,owner/repo2
20
+ # GITHUB_BOT_NAME=ove
21
+ # GITHUB_POLL_INTERVAL=30000
22
+
23
+ # CLI mode (auto-enabled when no other adapters configured)
24
+ # CLI_MODE=true
25
+
26
+ # Repos directory
27
+ REPOS_DIR=./repos
28
+
29
+ # Config file path (optional, defaults to ./config.json)
30
+ # CONFIG_PATH=./config.json
@@ -0,0 +1,16 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: oven-sh/setup-bun@v2
15
+ - run: bun install
16
+ - run: bun test
@@ -0,0 +1,33 @@
1
+ name: Deploy Pages
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths: [docs/**]
7
+
8
+ permissions:
9
+ contents: read
10
+ pages: write
11
+ id-token: write
12
+
13
+ concurrency:
14
+ group: pages
15
+ cancel-in-progress: true
16
+
17
+ jobs:
18
+ deploy:
19
+ environment:
20
+ name: github-pages
21
+ url: ${{ steps.deployment.outputs.page_url }}
22
+ runs-on: ubuntu-latest
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+
26
+ - uses: actions/configure-pages@v5
27
+
28
+ - uses: actions/upload-pages-artifact@v3
29
+ with:
30
+ path: docs
31
+
32
+ - id: deployment
33
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,45 @@
1
+ name: Publish Package
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ contents: read
9
+ packages: write
10
+
11
+ jobs:
12
+ test:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: oven-sh/setup-bun@v2
17
+ - run: bun install
18
+ - run: bun test
19
+
20
+ publish:
21
+ needs: test
22
+ runs-on: ubuntu-latest
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+
26
+ - uses: actions/setup-node@v4
27
+ with:
28
+ node-version: 20
29
+ registry-url: https://registry.npmjs.org
30
+
31
+ - uses: oven-sh/setup-bun@v2
32
+
33
+ - run: bun install
34
+
35
+ # Update version from release tag (safe: GITHUB_REF_NAME is not user-controlled)
36
+ - name: Set version from tag
37
+ env:
38
+ REF_NAME: ${{ github.ref_name }}
39
+ run: |
40
+ VERSION="${REF_NAME#v}"
41
+ npm version "$VERSION" --no-git-tag-version --allow-same-version
42
+
43
+ - run: npm publish
44
+ env:
45
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/CLAUDE.md ADDED
@@ -0,0 +1,28 @@
1
+ # Ove
2
+
3
+ Your grumpy but meticulous dev companion — routes chat messages to Claude Code CLI in isolated worktrees.
4
+
5
+ ## Stack
6
+ - Bun + TypeScript
7
+ - @slack/bolt (Socket Mode) for Slack
8
+ - baileys for WhatsApp
9
+ - grammy for Telegram
10
+ - discord.js for Discord
11
+ - Bun.serve for HTTP API + Web UI
12
+ - gh CLI for GitHub polling
13
+ - bun:sqlite for task queue
14
+ - claude -p CLI for code tasks
15
+
16
+ ## Structure
17
+ - src/adapters/ — chat platform adapters (Slack, WhatsApp, Telegram, Discord, CLI) and event adapters (GitHub, HTTP API)
18
+ - src/queue.ts — SQLite task queue
19
+ - src/runners/ — agent runner implementations (Claude, future Codex)
20
+ - src/repos.ts — git clone/pull/worktree management
21
+ - src/router.ts — message → task mapping
22
+ - src/config.ts — repo/user configuration
23
+
24
+ ## Conventions
25
+ - No classes unless necessary, prefer functions and plain objects
26
+ - Use bun:sqlite directly, no ORMs
27
+ - Use bun:test for testing
28
+ - Structured JSON logging via src/logger.ts
package/Dockerfile ADDED
@@ -0,0 +1,37 @@
1
+ FROM oven/bun:1 AS base
2
+
3
+ # System deps: git for repo management, ssh for private repos
4
+ RUN apt-get update && \
5
+ apt-get install -y --no-install-recommends git openssh-client && \
6
+ rm -rf /var/lib/apt/lists/*
7
+
8
+ # Claude CLI (installed via npm since bun global install has quirks)
9
+ RUN bunx --bun npm i -g @anthropic-ai/claude-code
10
+
11
+ WORKDIR /app
12
+
13
+ # Install dependencies
14
+ COPY package.json bun.lock ./
15
+ RUN bun install --frozen-lockfile --production
16
+
17
+ # Copy source
18
+ COPY bin/ bin/
19
+ COPY src/ src/
20
+ COPY tsconfig.json ./
21
+ COPY config.example.json .env.example ./
22
+
23
+ # Non-root user (Claude CLI refuses --dangerously-skip-permissions as root)
24
+ # Default UID/GID 1000 matches most host users; override with --build-arg
25
+ ARG UID=1000
26
+ ARG GID=1000
27
+ RUN groupadd -g $GID ove 2>/dev/null || true && \
28
+ useradd -m -s /bin/bash -u $UID -g $GID ove 2>/dev/null || true && \
29
+ mkdir -p repos && \
30
+ chown -R $UID:$GID /app
31
+ USER $UID
32
+
33
+ # Git safe.directory for mounted volumes
34
+ RUN git config --global --add safe.directory '*'
35
+
36
+ ENTRYPOINT ["bun", "run", "bin/ove.ts"]
37
+ CMD ["start"]
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Love Nyberg
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,183 @@
1
+ <p align="center">
2
+ <img src="logo.png" width="180" alt="Ove" />
3
+ </p>
4
+
5
+ <h1 align="center">Ove</h1>
6
+
7
+ <p align="center">
8
+ Your grumpy but meticulous dev companion.<br>
9
+ <a href="https://jacksoncage.github.io/ove">Docs</a> · <a href="https://github.com/jacksoncage/ove">GitHub</a> · <a href="https://github.com/jacksoncage/ove/pkgs/npm/ove">Package</a>
10
+ </p>
11
+
12
+ ---
13
+
14
+ Talk to Ove from Slack, WhatsApp, Telegram, Discord, GitHub issues, a Web UI, or the terminal — he'll grumble about it, but he'll review your PRs, fix your issues, run your tests, brainstorm ideas, and scaffold new projects. Properly.
15
+
16
+ ## Quick Start
17
+
18
+ ```bash
19
+ npm install -g @jacksoncage/ove
20
+ ove init # interactive setup — creates config.json and .env
21
+ ove start
22
+ ```
23
+
24
+ ## Prerequisites
25
+
26
+ - [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and authenticated
27
+ - [GitHub CLI](https://cli.github.com) (`gh`) installed and authenticated
28
+ - SSH access to your git repos
29
+
30
+ ## Commands
31
+
32
+ ```
33
+ review PR #N on <repo> Code review with inline comments
34
+ fix issue #N on <repo> Read issue, implement fix, create PR
35
+ simplify <path> in <repo> Reduce complexity, create PR
36
+ validate <repo> Run tests and linter
37
+ discuss <topic> Brainstorm ideas (no code changes)
38
+ create project <name> Scaffold a new project
39
+
40
+ Scheduling:
41
+ <task> every day at <time> Schedule a recurring task
42
+ list schedules See your scheduled tasks
43
+ remove schedule #N Remove a scheduled task
44
+
45
+ Meta:
46
+ status Queue stats
47
+ history Recent tasks
48
+ clear Reset conversation
49
+ ```
50
+
51
+ ## Deployment
52
+
53
+ Three ways to run Ove. Pick what fits. See the [full guide](https://jacksoncage.github.io/ove#getting-started) for details.
54
+
55
+ ### Local
56
+
57
+ ```bash
58
+ npm install -g @jacksoncage/ove
59
+ ove init
60
+ ove start
61
+ ```
62
+
63
+ Requires [Bun](https://bun.sh), Claude Code CLI, and GitHub CLI on your machine.
64
+
65
+ ### Docker
66
+
67
+ ```bash
68
+ ove init # generate config locally
69
+ docker compose up -d # start container
70
+ docker compose logs -f # watch logs
71
+ ```
72
+
73
+ The image includes Bun, git, and Claude CLI. Mounts `config.json`, `.env`, `repos/`, and SSH keys from the host.
74
+
75
+ ### VM
76
+
77
+ Ove runs well on a small VM (2 CPU, 4 GB RAM). Install Bun, Claude Code, and GitHub CLI, then run as a systemd service:
78
+
79
+ ```bash
80
+ git clone git@github.com:jacksoncage/ove.git && cd ove
81
+ bun install
82
+ ove init
83
+ sudo cp deploy/ove.service /etc/systemd/system/ove.service
84
+ sudo systemctl enable --now ove
85
+ ```
86
+
87
+ ## Transport Setup
88
+
89
+ ### Slack
90
+
91
+ 1. Create app at [api.slack.com/apps](https://api.slack.com/apps)
92
+ 2. Enable **Socket Mode** → generate App-Level Token (`xapp-...`)
93
+ 3. Bot scopes: `chat:write`, `channels:history`, `groups:history`, `im:history`, `mpim:history`, `app_mentions:read`
94
+ 4. Event subscriptions: `message.im`, `app_mention`
95
+ 5. **App Home** → Messages Tab → "Allow users to send messages"
96
+ 6. Install to workspace → copy Bot Token (`xoxb-...`)
97
+
98
+ ### Telegram
99
+
100
+ 1. Message [@BotFather](https://t.me/BotFather) → `/newbot`
101
+ 2. Copy the bot token
102
+ 3. Set `TELEGRAM_BOT_TOKEN=<token>` in `.env`
103
+
104
+ ### Discord
105
+
106
+ 1. Create app at [discord.com/developers](https://discord.com/developers/applications)
107
+ 2. Bot → enable **Message Content Intent**
108
+ 3. Copy bot token
109
+ 4. Invite bot to server with `bot` scope + `Send Messages`, `Read Message History`
110
+ 5. Set `DISCORD_BOT_TOKEN=<token>` in `.env`
111
+
112
+ ### HTTP API + Web UI
113
+
114
+ 1. Set `HTTP_API_PORT=3000` and `HTTP_API_KEY=<your-secret>` in `.env`
115
+ 2. Open `http://localhost:3000` for the Web UI
116
+ 3. Or call the API directly: `curl -X POST http://localhost:3000/api/message -H "X-API-Key: <key>" -H "Content-Type: application/json" -d '{"text": "validate my-app"}'`
117
+
118
+ ### GitHub (issue/PR comments)
119
+
120
+ 1. Set `GITHUB_POLL_REPOS=owner/repo1,owner/repo2` in `.env`
121
+ 2. Optionally set `GITHUB_BOT_NAME=ove` (default) and `GITHUB_POLL_INTERVAL=30000`
122
+ 3. Mention `@ove` in an issue or PR comment to trigger a task
123
+ 4. Ove replies with a comment when the task completes
124
+
125
+ ### WhatsApp
126
+
127
+ 1. Set `WHATSAPP_ENABLED=true` in `.env`
128
+ 2. Scan the QR code printed in the terminal on first start
129
+
130
+ ## Config
131
+
132
+ ```json
133
+ {
134
+ "repos": {
135
+ "my-app": {
136
+ "url": "git@github.com:org/my-app.git",
137
+ "defaultBranch": "main"
138
+ }
139
+ },
140
+ "users": {
141
+ "slack:U0ABC1234": { "name": "alice", "repos": ["my-app"] },
142
+ "telegram:123456789": { "name": "alice", "repos": ["my-app"] },
143
+ "discord:987654321": { "name": "alice", "repos": ["my-app"] },
144
+ "github:alice": { "name": "alice", "repos": ["my-app"] },
145
+ "http:anon": { "name": "alice", "repos": ["my-app"] },
146
+ "cli:local": { "name": "alice", "repos": ["my-app"] }
147
+ },
148
+ "claude": { "maxTurns": 10 },
149
+ "cron": [
150
+ {
151
+ "schedule": "0 9 * * 1-5",
152
+ "repo": "my-app",
153
+ "prompt": "Run lint and tests.",
154
+ "userId": "slack:U0ABC1234"
155
+ }
156
+ ]
157
+ }
158
+ ```
159
+
160
+ Static cron jobs go in `config.json`. Users can also create schedules via chat — these are stored in SQLite and managed with `list schedules` / `remove schedule #N`.
161
+
162
+ ## Testing
163
+
164
+ ```bash
165
+ bun test # 150 tests
166
+ ```
167
+
168
+ ## How It Works
169
+
170
+ 1. Message arrives via any transport (Slack, WhatsApp, Telegram, Discord, CLI, HTTP API, or GitHub comment)
171
+ 2. Chat adapters use `handleMessage`, event adapters use `handleEvent`
172
+ 3. Router parses intent and extracts repo/args
173
+ 4. Task gets queued in SQLite (one per repo at a time)
174
+ 5. Worker creates an isolated git worktree
175
+ 6. Runs `claude -p` with streaming NDJSON output
176
+ 7. Status updates stream back (chat: edits a message, HTTP: SSE, GitHub: single comment)
177
+ 8. Result sent back, worktree cleaned up
178
+
179
+ See [example conversations](docs/examples.md) for all flows.
180
+
181
+ ## License
182
+
183
+ MIT
package/bin/ove.ts ADDED
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { existsSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { createInterface } from "node:readline/promises";
6
+ import { validateConfig, runSetup } from "../src/setup";
7
+
8
+ const args = process.argv.slice(2);
9
+ const command = args[0];
10
+
11
+ if (command === "init") {
12
+ if (existsSync("config.json")) {
13
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
14
+ const answer = await rl.question(" Config already exists. Overwrite? (y/n): ");
15
+ rl.close();
16
+ if (answer.trim().toLowerCase() !== "y") {
17
+ console.log(" Aborted.");
18
+ process.exit(0);
19
+ }
20
+ }
21
+ await runSetup();
22
+ process.exit(0);
23
+ }
24
+
25
+ if (command === "start" || !command) {
26
+ process.stdout.write("\n Checking config...\n");
27
+ const { valid, issues } = validateConfig();
28
+
29
+ if (!valid) {
30
+ for (const issue of issues) {
31
+ process.stdout.write(` ⚠ ${issue}\n`);
32
+ }
33
+ process.stdout.write("\n");
34
+
35
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
36
+ const answer = await rl.question(" Want to fix these now? (y/n): ");
37
+ rl.close();
38
+
39
+ if (answer.trim().toLowerCase() === "y") {
40
+ await runSetup({ fixOnly: issues });
41
+ }
42
+ }
43
+
44
+ process.stdout.write(" Starting Ove...\n\n");
45
+ const entry = join(import.meta.dir, "..", "src", "index.ts");
46
+ await import(entry);
47
+ } else if (command === "test") {
48
+ console.log("Run: bun test");
49
+ process.exit(0);
50
+ } else if (command === "help" || command === "--help" || command === "-h") {
51
+ console.log(`
52
+ ove - Your grumpy but meticulous dev companion
53
+
54
+ Usage:
55
+ ove Start Ove (auto-detects Slack/CLI from env)
56
+ ove start Same as above
57
+ ove init Interactive setup — creates config.json and .env
58
+ ove help Show this message
59
+
60
+ Environment:
61
+ SLACK_BOT_TOKEN Slack bot token (xoxb-...)
62
+ SLACK_APP_TOKEN Slack app token (xapp-...)
63
+ CLI_MODE=true Force CLI mode
64
+ REPOS_DIR Directory for git repos (default: ./repos)
65
+
66
+ More info: https://jacksoncage.github.io/ove
67
+ `);
68
+ process.exit(0);
69
+ } else {
70
+ console.log(`Unknown command: ${command}. Run 'ove help' for usage.`);
71
+ process.exit(1);
72
+ }