-
Notifications
You must be signed in to change notification settings - Fork 1
Contributing
jstuart0 edited this page Apr 24, 2026
·
1 revision
AgentPulse is early — a small cohesive codebase where each feature touches 3–8 files. That's a great place to learn Bun + Hono + Drizzle + React 19 by shipping real features, and a frustrating place to drop a monster PR that touches 80 files. Both can happen; pick one.
- 10 good first issues are tagged with file paths + acceptance criteria. Start with those.
- Open-source contributions of any size are welcome — small ones land fast, big ones need a short design comment on the issue first.
# Requires Bun 1.x and Node 20+ for the Vite dev server proxy.
git clone https://github.com/jstuart0/agentpulse
cd agentpulse
bun install
# Local SQLite DB at ./data/agentpulse.db
bun run devbun run dev starts the API (port 3000) + Vite dev server (port 5173) concurrently. The frontend proxies /app-api/v1/* to the API so cookie auth works.
For the AI control plane to actually work, also set:
export AGENTPULSE_AI_ENABLED=true
export AGENTPULSE_SECRETS_KEY=test-key-32-chars-long-dev-only-yeahbun run dev |
API + Vite dev server |
bun run dev:server |
API only |
bun run typecheck |
tsc --noEmit — pre-commit sanity |
bun test |
Full test suite |
bunx biome check --fix src/ |
Lint + auto-fix |
bun run build |
Production build (catches Vite errors) |
src/
├── server/
│ ├── routes/ HTTP endpoints (Hono routers)
│ ├── services/ Business logic
│ │ ├── ai/ Watcher, LLM adapters, classifier, inbox, digest
│ │ ├── ask/ Global Ask chat (resolver, context, service)
│ │ ├── channels/ Notification channels (Telegram today)
│ │ └── *.ts Session tracker, name generator, telemetry, …
│ ├── db/ Drizzle schema + client + migrations
│ ├── auth/ API key, Authentik header, local session, supervisor token
│ └── ws/ WebSocket handler with pub/sub
├── web/
│ ├── pages/ Top-level routes (Dashboard, SessionDetail, Ask, …)
│ ├── components/ Reusable UI
│ ├── stores/ Zustand stores (sessions, events, labs, user, prefs, tabs)
│ ├── hooks/ useSessions, useWebSocket
│ └── lib/ API client, theme, utils
├── shared/ Types shared between server + web
└── supervisor/ Per-machine capability + launch reporting (early)
deploy/k8s/ Reference k8s manifests
scripts/ setup.sh, setup-relay.sh, relay.ts, statusline.sh, …
telemetry-worker/ Cloudflare Worker (opt-out usage pings)
thoughts/ Research + plans (not checked into public repo history)
-
Biome for formatting (tabs, double quotes, semicolons). Run
bunx biome check --fixbefore committing. -
Drizzle schema — every new table goes in
src/server/db/schema.tsAND gets a matchingCREATE TABLE IF NOT EXISTSinsrc/server/db/client.ts. Migrations for existing deploys areALTER TABLE ADD COLUMNlines in themigrationsarray (append-only; idempotent; retries on lock). -
No floating promises — use
void foo()orawait. -
Tests live next to code —
foo.test.tsbesidefoo.ts. Use the sharedsrc/server/services/ai/__test_db.tshelper for tests that need a real DB. - Hook ingest must be fast — < 50ms response, always return 200. Real work goes into the event processor which runs async after the response.
-
SQLite datetime —
YYYY-MM-DD HH:MM:SSwith no T/Z. UseparseDate()in the frontend. -
Session names generated from adjective-noun pairs (
name-generator.ts). Don't expose raw UUIDs in UI copy. -
Feature flags — Labs flags (
labs-service.ts) gate UI visibility. AI runtime toggles gate behavior.AGENTPULSE_AI_ENABLEDgates whether the feature is compiled in at all. -
Secrets encrypted at rest — anything sensitive (LLM keys, bot tokens, webhook secrets) goes through
services/ai/secrets.ts(AES-256-GCM with scrypt-derived keys). Store innotification_channels.credential_ciphertextorsettingsJSON blobs.
See good-first-issue #1 (Mistral) for the walkthrough. Short version:
- New file under
src/server/services/ai/llm/<name>.tsimplementingLlmAdapter. - Add the kind to
ProviderKindintypes.ts. - Wire it into
registry.ts'sgetAdapter(). - Pricing in
pricing.ts. - UI dropdown entry in
AiSettingsPanel.tsx. - Tests mocking happy-path + a 5xx + auth failure.
- If the provider supports streaming, implement
completeStream()for Ask.
See good-first-issue #2 (Slack) for the walkthrough. Short version:
- New file under
src/server/services/channels/<name>.tsimplementingNotificationChannelAdapter. - Add the kind to
NotificationChannelKindinchannels/types.ts. - Wire into the registry + channel routes.
- Build a
<Name>ChannelPanel.tsxmodeled onTelegramChannelPanel.tsx. - If the channel has interactive callbacks (button taps), add a signed webhook route outside the
apiauth bundle. - Tests for signature verification + message formatting.
- The
apiHono sub-bundle gets mounted under both/apiand/app-api. Public routes (webhooks, hooks) must be mounted outside it because sibling routers'.use("*", requireAuth())cascade across the whole bundle when merged viaapi.route(). - When returning streaming responses, don't use Hono's
streamSSEhelper — it setsTransfer-Encoding: chunked, forbidden on HTTP/2. Use a plainnew Response(readableStream, { headers })withContent-Type: text/event-stream. - The watcher should never subscribe to
session_updated— every hook ingest and the 60-second stale sweeper emit that event, flooding the queue. Usesession_eventonly.
- Fork, branch from
dev(notmain). - Ship the smallest unit that makes sense as a PR. 3–8 files is great. 50+ files needs a design comment on the issue first.
- Write tests for the happy path + the edge case you thought about while writing the feature.
- Biome clean, typecheck clean, tests green locally.
- Open the PR against
dev. CI runs the same checks. - Maintainer review usually within 48h.
Be nice. Be specific. Ship small. Reality-check your own PRs before submitting — if you wouldn't review this in 10 minutes, break it up.