OpenAPI spec (lib/api-spec/openapi.yaml)
- Added POST /jobs, GET /jobs/{id}, GET /demo endpoints
- Added schemas: CreateJobRequest, CreateJobResponse, JobStatusResponse,
InvoiceInfo, JobState, DemoResponse, ErrorResponse
- Ran codegen: generated CreateJobBody, GetJobResponse, RunDemoQueryParams etc.
Jobs router (artifacts/api-server/src/routes/jobs.ts)
- POST /jobs: validates body, creates LNbits eval invoice, inserts job +
invoice in a DB transaction, returns { jobId, evalInvoice }
- GET /jobs/🆔 fetches job, calls advanceJob() helper, returns state-
appropriate payload (eval/work invoice, reason, result, errorMessage)
- advanceJob() state machine:
- awaiting_eval_payment: checks LNbits, atomically marks paid + advances
state via optimistic WHERE state='awaiting_eval_payment'; runs
AgentService.evaluateRequest, branches to awaiting_work_payment or rejected
- awaiting_work_payment: same pattern for work invoice, runs
AgentService.executeWork, advances to complete
- Any agent/LNbits error transitions job to failed
Demo router (artifacts/api-server/src/routes/demo.ts)
- GET /demo?request=...: in-memory rate limiter (5 req/hour per IP)
- Explicit guard for missing request param (coerce.string() workaround)
- Calls AgentService.executeWork directly, returns { result }
Dev router (artifacts/api-server/src/routes/dev.ts)
- POST /dev/stub/pay/:paymentHash: marks stub invoice paid in-memory
- Only mounted when NODE_ENV !== 'production'
Route index updated to mount all three routers
replit.md: documented full curl flow with all 6 steps, demo endpoint,
and dev stub-pay trigger
End-to-end verified with curl:
- Full flow: create → eval pay → evaluating → work pay → executing → complete
- Error cases: 400 on missing body/param, 404 on unknown job
161 lines
7.4 KiB
Markdown
161 lines
7.4 KiB
Markdown
# Workspace
|
|
|
|
## Overview
|
|
|
|
pnpm workspace monorepo using TypeScript. Each package manages its own dependencies.
|
|
|
|
## Stack
|
|
|
|
- **Monorepo tool**: pnpm workspaces
|
|
- **Node.js version**: 24
|
|
- **Package manager**: pnpm
|
|
- **TypeScript version**: 5.9
|
|
- **API framework**: Express 5
|
|
- **Database**: PostgreSQL + Drizzle ORM
|
|
- **Validation**: Zod (`zod/v4`), `drizzle-zod`
|
|
- **API codegen**: Orval (from OpenAPI spec)
|
|
- **Build**: esbuild (CJS bundle)
|
|
|
|
## Structure
|
|
|
|
```text
|
|
artifacts-monorepo/
|
|
├── artifacts/ # Deployable applications
|
|
│ └── api-server/ # Express API server
|
|
├── lib/ # Shared libraries
|
|
│ ├── api-spec/ # OpenAPI spec + Orval codegen config
|
|
│ ├── api-client-react/ # Generated React Query hooks
|
|
│ ├── api-zod/ # Generated Zod schemas from OpenAPI
|
|
│ └── db/ # Drizzle ORM schema + DB connection
|
|
├── scripts/ # Utility scripts (single workspace package)
|
|
│ └── src/ # Individual .ts scripts, run via `pnpm --filter @workspace/scripts run <script>`
|
|
├── pnpm-workspace.yaml # pnpm workspace (artifacts/*, lib/*, lib/integrations/*, scripts)
|
|
├── tsconfig.base.json # Shared TS options (composite, bundler resolution, es2022)
|
|
├── tsconfig.json # Root TS project references
|
|
└── package.json # Root package with hoisted devDeps
|
|
```
|
|
|
|
## TypeScript & Composite Projects
|
|
|
|
Every package extends `tsconfig.base.json` which sets `composite: true`. The root `tsconfig.json` lists all packages as project references. This means:
|
|
|
|
- **Always typecheck from the root** — run `pnpm run typecheck` (which runs `tsc --build --emitDeclarationOnly`). This builds the full dependency graph so that cross-package imports resolve correctly. Running `tsc` inside a single package will fail if its dependencies haven't been built yet.
|
|
- **`emitDeclarationOnly`** — we only emit `.d.ts` files during typecheck; actual JS bundling is handled by esbuild/tsx/vite...etc, not `tsc`.
|
|
- **Project references** — when package A depends on package B, A's `tsconfig.json` must list B in its `references` array. `tsc --build` uses this to determine build order and skip up-to-date packages.
|
|
|
|
## Root Scripts
|
|
|
|
- `pnpm run build` — runs `typecheck` first, then recursively runs `build` in all packages that define it
|
|
- `pnpm run typecheck` — runs `tsc --build --emitDeclarationOnly` using project references
|
|
|
|
## Environment Variables & Secrets
|
|
|
|
### Automatically provisioned (do not set manually)
|
|
|
|
| Secret | Purpose |
|
|
|---|---|
|
|
| `AI_INTEGRATIONS_ANTHROPIC_BASE_URL` | Replit AI Integrations proxy base URL for Anthropic |
|
|
| `AI_INTEGRATIONS_ANTHROPIC_API_KEY` | Replit AI Integrations proxy API key (dummy value, auto-managed) |
|
|
| `DATABASE_URL` | PostgreSQL connection string (Replit-managed) |
|
|
| `SESSION_SECRET` | Express session secret (Replit-managed) |
|
|
|
|
### Required secrets (set via Replit Secrets tab)
|
|
|
|
| Secret | Description | Example |
|
|
|---|---|---|
|
|
| `LNBITS_URL` | Base URL of your LNbits instance | `https://legend.lnbits.com` |
|
|
| `LNBITS_API_KEY` | Invoice/Admin API key from your LNbits wallet | `a3f...` |
|
|
|
|
> **Note:** If `LNBITS_URL` and `LNBITS_API_KEY` are absent, `LNbitsService` automatically runs in **stub mode** — invoices are simulated in-memory and can be marked paid via `svc.stubMarkPaid(hash)`. This is intentional for development without a Lightning node.
|
|
|
|
## Packages
|
|
|
|
### `artifacts/api-server` (`@workspace/api-server`)
|
|
|
|
Express 5 API server. Routes live in `src/routes/` and use `@workspace/api-zod` for request and response validation and `@workspace/db` for persistence.
|
|
|
|
- Entry: `src/index.ts` — reads `PORT`, starts Express
|
|
- App setup: `src/app.ts` — mounts CORS, JSON/urlencoded parsing, routes at `/api`
|
|
- Routes: `src/routes/index.ts` mounts sub-routers; `src/routes/health.ts` exposes `GET /health` (full path: `/api/health`)
|
|
- Depends on: `@workspace/db`, `@workspace/api-zod`
|
|
- `pnpm --filter @workspace/api-server run dev` — run the dev server
|
|
- `pnpm --filter @workspace/api-server run build` — production esbuild bundle (`dist/index.cjs`)
|
|
- Build bundles an allowlist of deps (express, cors, pg, drizzle-orm, zod, etc.) and externalizes the rest
|
|
|
|
### `lib/db` (`@workspace/db`)
|
|
|
|
Database layer using Drizzle ORM with PostgreSQL. Exports a Drizzle client instance and schema models.
|
|
|
|
- `src/index.ts` — creates a `Pool` + Drizzle instance, exports schema
|
|
- `src/schema/index.ts` — barrel re-export of all models
|
|
- `src/schema/<modelname>.ts` — table definitions with `drizzle-zod` insert schemas (no models definitions exist right now)
|
|
- `drizzle.config.ts` — Drizzle Kit config (requires `DATABASE_URL`, automatically provided by Replit)
|
|
- Exports: `.` (pool, db, schema), `./schema` (schema only)
|
|
|
|
Production migrations are handled by Replit when publishing. In development, we just use `pnpm --filter @workspace/db run push`, and we fallback to `pnpm --filter @workspace/db run push-force`.
|
|
|
|
### `lib/api-spec` (`@workspace/api-spec`)
|
|
|
|
Owns the OpenAPI 3.1 spec (`openapi.yaml`) and the Orval config (`orval.config.ts`). Running codegen produces output into two sibling packages:
|
|
|
|
1. `lib/api-client-react/src/generated/` — React Query hooks + fetch client
|
|
2. `lib/api-zod/src/generated/` — Zod schemas
|
|
|
|
Run codegen: `pnpm --filter @workspace/api-spec run codegen`
|
|
|
|
### `lib/api-zod` (`@workspace/api-zod`)
|
|
|
|
Generated Zod schemas from the OpenAPI spec (e.g. `HealthCheckResponse`). Used by `api-server` for response validation.
|
|
|
|
### `lib/api-client-react` (`@workspace/api-client-react`)
|
|
|
|
Generated React Query hooks and fetch client from the OpenAPI spec (e.g. `useHealthCheck`, `healthCheck`).
|
|
|
|
### `artifacts/api-server` — Timmy API endpoints
|
|
|
|
#### Payment-gated job flow
|
|
|
|
```bash
|
|
BASE="https://${REPLIT_DEV_DOMAIN}" # or http://localhost:8080 in dev
|
|
|
|
# 1. Create a job (returns eval invoice)
|
|
curl -s -X POST "$BASE/api/jobs" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"request": "Write a haiku about lightning payments"}'
|
|
# → {"jobId":"…","evalInvoice":{"paymentRequest":"lnbcrt10u1…","amountSats":10}}
|
|
|
|
# 2. Poll status (returns eval invoice while unpaid)
|
|
curl -s "$BASE/api/jobs/<jobId>"
|
|
|
|
# 3. (Stub mode only) Mark eval invoice paid
|
|
curl -s -X POST "$BASE/api/dev/stub/pay/<paymentHash>"
|
|
|
|
# 4. Poll again — auto-advances to awaiting_work_payment, returns work invoice
|
|
curl -s "$BASE/api/jobs/<jobId>"
|
|
|
|
# 5. (Stub mode only) Mark work invoice paid
|
|
curl -s -X POST "$BASE/api/dev/stub/pay/<workPaymentHash>"
|
|
|
|
# 6. Poll again — auto-advances to complete, returns result
|
|
curl -s "$BASE/api/jobs/<jobId>"
|
|
# → {"jobId":"…","state":"complete","result":"…"}
|
|
```
|
|
|
|
Job states: `awaiting_eval_payment` → `evaluating` → `awaiting_work_payment` → `executing` → `complete` | `rejected` | `failed`
|
|
|
|
#### Free demo endpoint (rate-limited: 5 req/hour per IP)
|
|
|
|
```bash
|
|
curl -s "$BASE/api/demo?request=Explain+proof+of+work+in+one+sentence"
|
|
# → {"result":"…"}
|
|
```
|
|
|
|
#### Dev-only stub payment trigger
|
|
|
|
`POST /api/dev/stub/pay/:paymentHash` — marks a stub invoice paid in-memory.
|
|
Only available in development (`NODE_ENV !== 'production'`).
|
|
|
|
### `scripts` (`@workspace/scripts`)
|
|
|
|
Utility scripts package. Each script is a `.ts` file in `src/` with a corresponding npm script in `package.json`. Run scripts via `pnpm --filter @workspace/scripts run <script>`. Scripts can import any workspace package (e.g., `@workspace/db`) by adding it as a dependency in `scripts/package.json`.
|