Commit Graph

41 Commits

Author SHA1 Message Date
Replit Agent
db28efca6d fix: set artifact previewPath to / so landing page and /tower route in production 2026-03-19 14:15:13 +00:00
Replit Agent
add08e363a fix: use process.cwd() for tower path — import.meta.url is undefined in CJS bundle 2026-03-19 13:59:57 +00:00
Replit Agent
9de2396457 feat: Alexander Whitestone landing page + the-matrix dist at /tower
- Root / serves branded landing page (falling amber digit rain, enter button)
- /tower serves pre-built the-matrix frontend (Three.js Workshop world)
- config.js patched: WS URL auto-detects from window.location.host
- No manual ?ws= param needed — works on any domain
2026-03-19 07:12:26 +00:00
alexpaynex
abe9c221c7 feat(task-25): real LNbits mode on Hermes VPS — 29/29 testkit PASS
Task #25: Provision LNbits on Hermes VPS for real Lightning payments.

## Infrastructure (Hermes VPS 143.198.27.163)
- PostgreSQL 16 installed, lnbits DB + user created
- LNbits 0.12.12 installed in /opt/lnbits/.venv (Python 3.11 venv)
- /opt/lnbits/run.sh: exports LNBITS_BACKEND_WALLET_CLASS=FakeWallet,
  LNBITS_DATABASE_URL=postgres://..., starts lnbits on 0.0.0.0:5000
- systemd unit at /etc/systemd/system/lnbits.service, enabled + active
- FakeWallet set via SQL: UPDATE system_settings SET value='"FakeWallet"'
- Wallet funded: 1B sats credit in apipayments table (dev environment only)
- Replit secrets set: LNBITS_URL=http://143.198.27.163:5000, LNBITS_API_KEY=...

## Provisioning runbook
- scripts/hermes-lnbits/provision.sh: idempotent Ubuntu 24.04 setup script
  covering PostgreSQL, venv, run.sh, systemd unit, FakeWallet SQL, health check

## API server code changes (real-mode plumbing)
- lib/lnbits.ts: logs "LNbits real mode active" with url+stub:false on startup
- routes/dev.ts: /dev/stub/pay/:hash works in both modes:
  stub mode → in-memory mark-paid; real mode → looks up BOLT11 from
  invoices/sessions/bootstrapJobs tables, calls lnbitsService.payInvoice()
- routes/sessions.ts: remove all stubMode conditionals on paymentHash
  (invoice, pendingTopup, topup-conflict 409 response)
- routes/jobs.ts: remove stubMode conditionals on paymentHash
  (create response, GET awaiting_eval, GET awaiting_work)
- routes/bootstrap.ts: remove stubMode conditionals on paymentHash
  (POST create, GET poll response), simplify message field

## Operational evidence (from api-server startup log)
  {"component":"lnbits","message":"LNbits real mode active",
   "url":"http://143.198.27.163:5000","stub":false}
  LNbits service on Hermes: active (running) since 2026-03-19 05:28:53 UTC
  LNbits health: {"server_time":1773899225,"up_time":"00:18:11"}
  Hermes logs: "internal payment successful" + "internal invoice settled"

## Testkit: PASS=29 FAIL=0 SKIP=0 (real LNbits mode, 2026-03-19 05:48)
2026-03-19 05:49:46 +00:00
alexpaynex
76ed359bb1 feat: real LNbits mode support — 29/29 testkit PASS
Task #25: Provision LNbits on Hermes VPS for real Lightning payments.

Changes:
- dev.ts: /dev/stub/pay/:hash now works in both stub and real LNbits modes.
  In real mode, looks up BOLT11 from invoices/sessions/bootstrapJobs tables
  then calls lnbitsService.payInvoice() (FakeWallet accepts it).
- sessions.ts: Remove all stubMode conditionals on paymentHash — always expose
  paymentHash in invoice, pendingTopup, and 409-conflict responses.
- jobs.ts: Remove stubMode conditionals on paymentHash in create, GET awaiting_eval,
  and GET awaiting_work responses.
- bootstrap.ts: Remove stubMode conditionals on paymentHash in POST create and
  GET poll responses. Simplify message field (no longer mode-conditional).
- Hermes VPS: Funded LNbits wallet with 1B sats via DB credit so payInvoice
  calls succeed (FakeWallet checks wallet balance before routing).

Result: 29/29 testkit PASS in real LNbits mode (LNBITS_URL + LNBITS_API_KEY set).
2026-03-19 05:44:35 +00:00
alexpaynex
ae25bfdf71 Improve test reliability by adding explicit checks for bootstrap process
Update testkit.ts to add explicit failure conditions for missing payment hash in stub mode and to assert that the bootstrapJobId returned in the poll response matches the created ID.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 9114d92d-daf7-42ae-a3f7-be296300efa5
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9f85e954-647c-46a5-90a7-396e495a805a/418bf6f8-212b-4bb0-a7a5-8231a061da4e/Q83Uqvu
Replit-Helium-Checkpoint-Created: true
2026-03-19 04:08:01 +00:00
alexpaynex
031ca5a5c3 task(#24): Bootstrap route + cost-ledger testkit coverage — 29/29 PASS
Task: Add T23 (bootstrap stub flow) and T24 (cost-ledger completeness) to the
testkit, bringing total from 27/27 to 29/29 PASS with 0 FAIL, 0 SKIP.

## What was changed
- `artifacts/api-server/src/routes/testkit.ts`:
  - Updated audit-log comment block to document T23 + T24 additions.
  - Inserted Test 23 after T22 (line ~654):
      POST /api/bootstrap → assert 201 + bootstrapJobId present.
      Guard on stubMode=true; SKIP if real DO mode (prevents hanging).
      Stub-pay the paymentHash via /api/dev/stub/pay/:hash.
      Poll GET /api/bootstrap/:id every 2s (20s timeout) until
      state=provisioning or state=ready; assert message field present.
  - Inserted Test 24 after T23:
      Guarded on STATE_T6=complete (reuses completed job from T6).
      GET /api/jobs/:id, extract costLedger.
      Assert all 8 fields non-null: actualInputTokens, actualOutputTokens,
        totalTokens, actualCostUsd, actualAmountSats, workAmountSats,
        refundAmountSats, refundState.
      Honest-accounting invariant: actualAmountSats <= workAmountSats.
      refundAmountSats >= 0.
      refundState must match ^(not_applicable|pending|paid)$.

## No deviations from task spec
- T23 guard logic matches spec exactly (stubMode check before poll).
- T24 fields match the 8 specified in task-24.md plus the invariants.
- No changes to bootstrap.ts or jobs.ts — existing routes already correct.

## Test run result
29/29 PASS, 0 FAIL, 0 SKIP (fresh server restart, rate-limit slots clean).
T23: state=provisioning in 1s. T24: actualAmountSats(179)<=workAmountSats(182),
refundAmountSats=3, refundState=pending.
2026-03-19 04:04:49 +00:00
alexpaynex
ad63b01223 Harden rate limit by using server-trusted IP address
Update rate limiting logic to use the server's IP address (extracted from request headers or socket) instead of the client-provided visitorId to prevent spoofing.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 892ae0fb-898b-4f34-949e-7a240560fe8e
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9f85e954-647c-46a5-90a7-396e495a805a/418bf6f8-212b-4bb0-a7a5-8231a061da4e/Q83Uqvu
Replit-Helium-Checkpoint-Created: true
2026-03-19 02:56:36 +00:00
alexpaynex
71dbbd3f37 feat(task-20): Timmy responds to Workshop input bar with AI
## Task
Task #20: Timmy responds to Workshop input bar — make the "Say something
to Timmy…" input bar actually trigger an AI response shown in Timmy's
speech bubble.

## What was built

### Server (artifacts/api-server/src/lib/agent.ts)
- Added `chatReply(userText)` method to AgentService
- Uses claude-haiku (cheaper eval model) with a wizard persona system prompt
- 150-token limit so replies fit in the speech bubble
- Stub mode: returns one of 4 wizard-themed canned replies after 400ms delay
- Real mode: calls Anthropic with wizard persona, truncates to 250 chars

### Server (artifacts/api-server/src/routes/events.ts)
- Imported agentService
- Added per-visitor rate limit system: 3 replies/minute per visitorId (in-memory Map)
- Added broadcastToAll() helper for broadcasting to all WS clients
- Updated visitor_message handler:
  1. Broadcasts visitor message to all watchers as before
  2. Checks rate limit — if exceeded, sends polite "I need a moment…" reply
  3. Fire-and-forget async AI call:
     - Broadcasts agent_state: gamma=working (crystal ball pulses)
     - Calls agentService.chatReply()
     - Broadcasts agent_state: gamma=idle
     - Broadcasts chat: agentId=timmy, text=reply to ALL clients
     - Logs world event "visitor:reply"

### Frontend (the-matrix/js/websocket.js)
- Updated case 'chat' handler to differentiate message sources:
  - agentId === 'timmy': speech bubble + event log entry "Timmy: <text>"
  - agentId === 'visitor': event log only (don't hijack speech bubble)
  - everything else (delta/alpha/beta payment notifications): speech bubble

## What was already working (no change needed)
- Enter key on input bar (ui.js already had keydown listener)
- Input clearing after send (already in ui.js)
- Speech bubble rendering (setSpeechBubble already existed in agents.js)
- WebSocket sendVisitorMessage already exported from websocket.js

## Tests
- 27/27 testkit PASS (no regressions)
- TypeScript: 0 errors
- Vite build: clean (the-matrix rebuilt)
2026-03-19 02:52:49 +00:00
4f7a5e9998 test: audit testkit — remove T3b inflation, add T17-T22 (27/27 PASS) (#32) 2026-03-18 22:34:12 -04:00
a70898e939 feat(epic222): Workshop — Timmy as wizard presence, world state, WS bootstrap (#31) 2026-03-18 22:15:46 -04:00
ea4cddc2ad fix(api): completedAt: null on non-complete states + OpenAPI timestamps & rate-limit headers (#29) 2026-03-18 21:49:51 -04:00
b929e6d72f feat(api): X-RateLimit-* headers on /api/demo + createdAt/completedAt on job responses (#19) (#28) 2026-03-18 21:41:14 -04:00
e088ca4cd8 feat(integration): WS bridge + Tower + payment panel + E2E test [10/10 PASS] (#26) 2026-03-18 21:20:51 -04:00
83a2ec19e2 fix(testkit): macOS compat + fix test 8c ordering (#24) 2026-03-18 21:01:13 -04:00
alexpaynex
ca94c0a9e5 Add Bitcoin/LND/LNbits local node setup scripts and node diagnostics endpoint
- scripts/bitcoin-ln-node/setup.sh: one-shot installer for Bitcoin Core (pruned mainnet), LND, and LNbits on Apple Silicon Mac. Generates secrets, writes configs, installs launchd plists for auto-start.
- scripts/bitcoin-ln-node/start.sh: start all services via launchctl; waits for RPC readiness and auto-unlocks LND wallet.
- scripts/bitcoin-ln-node/stop.sh: graceful shutdown (lncli stop → bitcoin-cli stop).
- scripts/bitcoin-ln-node/status.sh: full health check (Bitcoin sync %, LND channels/balance, LNbits HTTP, bore tunnel). Supports --json mode for machine consumption.
- scripts/bitcoin-ln-node/expose.sh: opens bore tunnel from LNbits port 5000 to bore.pub for Replit access.
- scripts/bitcoin-ln-node/get-lnbits-key.sh: fetches LNbits admin API key and prints Replit secret values.
- artifacts/api-server/src/routes/node-diagnostics.ts: GET /api/admin/node-status (JSON) and /api/admin/node-status/html — Timmy self-diagnoses its LNbits/LND connectivity and reports issues.
2026-03-18 21:58:41 +00:00
alexpaynex
4dd3c7f692 Show the application's public URL in server logs
Log the full public UI URL using the REPLIT_DEV_DOMAIN environment variable.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: a00ebe7c-c8e0-4118-81aa-ae93770e942f
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9f85e954-647c-46a5-90a7-396e495a805a/418bf6f8-212b-4bb0-a7a5-8231a061da4e/Q83Uqvu
Replit-Helium-Checkpoint-Created: true
2026-03-18 21:02:06 +00:00
alexpaynex
b02efc9057 Make job evaluation and execution run in the background
Refactors `runEvalInBackground` and `runWorkInBackground` to execute AI tasks asynchronously. Updates `pollJob` in `ui.ts` to handle 'evaluating', 'executing', and 'failed' states, and corrects `data.status` to `data.state` and `data.rejectionReason` to `data.reason`.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: ecf857ee-fa4d-47db-b4c1-b374ffb3815d
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9f85e954-647c-46a5-90a7-396e495a805a/418bf6f8-212b-4bb0-a7a5-8231a061da4e/Q83Uqvu
Replit-Helium-Checkpoint-Created: true
2026-03-18 21:00:43 +00:00
alexpaynex
e44d64ac44 Add payment hash to job creation response in stub mode
Include the `paymentHash` in the `evalInvoice` object when creating a job in stub mode via `POST /api/jobs` to ensure the frontend receives it correctly.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 7c57683f-4afc-46df-83b9-8b259c160aea
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9f85e954-647c-46a5-90a7-396e495a805a/418bf6f8-212b-4bb0-a7a5-8231a061da4e/Q83Uqvu
Replit-Helium-Checkpoint-Created: true
2026-03-18 20:36:49 +00:00
alexpaynex
adde196a40 Task #7: Redirect root to Timmy UI
Added two redirect routes in artifacts/api-server/src/app.ts:
- GET / → 302 redirect to /api/ui
- GET /api → 302 redirect to /api/ui

This means opening the preview URL or the root of the app immediately
lands on the Timmy UI without any manual navigation.

No changes to the UI itself, no new routes, no new files.
Verified: both / and /api return HTTP 302 with Location: /api/ui.
2026-03-18 20:16:48 +00:00
alexpaynex
ab2cc06a79 Add session mode for pre-funded request processing
Implement session-based API endpoints for creating, managing, and interacting with pre-funded sessions, including deposit and top-up invoice generation, macaroon authentication, and per-request debiting of compute costs.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 2dc3847e-7186-4a22-9c7e-16cd31bca8d9
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9f85e954-647c-46a5-90a7-396e495a805a/418bf6f8-212b-4bb0-a7a5-8231a061da4e/sPDHkg8
Replit-Helium-Checkpoint-Created: true
2026-03-18 20:00:24 +00:00
alexpaynex
dfc9ecdc7b Add honest accounting and automatic refund mechanism for completed jobs
Implement honest accounting post-job completion, calculating actual costs, adding margin, and enabling automatic refunds for overpayments via a new endpoint.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: c6386de2-d5f4-47cc-a557-73416f09e118
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9f85e954-647c-46a5-90a7-396e495a805a/418bf6f8-212b-4bb0-a7a5-8231a061da4e/sPDHkg8
Replit-Helium-Checkpoint-Created: true
2026-03-18 19:32:34 +00:00
alexpaynex
e5bdae7159 Task #6: Cost-based work fee pricing with BTC oracle
## New files
- btc-oracle.ts: CoinGecko BTC/USD fetch (60s cache), usdToSats() helper (ceil, min 1 sat),
  5s abort timeout, fallback to BTC_PRICE_USD_FALLBACK env var (default $100k)
- lib/db/migrations/0002_cost_based_pricing.sql: SQL migration artifact adding 6 new columns
  to jobs table (estimated_cost_usd, margin_pct, btc_price_usd, actual_input_tokens,
  actual_output_tokens, actual_cost_usd); idempotent via ADD COLUMN IF NOT EXISTS

## Modified files
- pricing.ts: Full rewrite — per-model token rates (Haiku/Sonnet, env-var overridable),
  DO infra amortisation per request, originator margin %, estimateInputTokens/Output by tier,
  calculateActualCostUsd() for post-work ledger, async calculateWorkFeeSats() → WorkFeeBreakdown
- agent.ts: WorkResult now includes inputTokens + outputTokens from Anthropic usage;
  workModel/evalModel exposed as readonly public; EVAL_MODEL/WORK_MODEL env var support
- lib/db/src/schema/jobs.ts: 6 new real/integer columns; schema pushed to DB
- jobs.ts route: Work invoice creation calls pricingService.calculateWorkFeeSats() async;
  stores estimatedCostUsd/marginPct/btcPriceUsd; post-work stores actualInputTokens/
  actualOutputTokens/actualCostUsd; GET response includes pricingBreakdown and costLedger
  with totalTokens (input + output computed field)
- openapi.yaml: PricingBreakdown + CostLedger schemas (with totalTokens) added
- lib/api-zod/src/generated/api.ts: Regenerated with new schemas
- lib/api-client-react/src/generated/api.schemas.ts: Regenerated (PricingBreakdown, CostLedger)
- replit.md: 17 new env vars documented in cost-based pricing section
2026-03-18 19:25:06 +00:00
alexpaynex
69eba6190d Task #6: Cost-based work fee pricing with BTC oracle
- btc-oracle.ts: CoinGecko BTC/USD fetch (60s cache), usdToSats() helper,
  fallback to BTC_PRICE_USD_FALLBACK env var (default $100k), 5s abort timeout
- pricing.ts: Full rewrite — per-model token rates (Haiku/Sonnet, env-var
  overridable), DO infra amortisation, originator margin %, estimateInputTokens(),
  estimateOutputTokens() by request tier, calculateActualCostUsd() for post-work ledger,
  async calculateWorkFeeSats() → WorkFeeBreakdown
- agent.ts: WorkResult now includes inputTokens + outputTokens from Anthropic usage;
  workModel/evalModel exposed as readonly public; EVAL_MODEL/WORK_MODEL env var support
- jobs.ts: Work invoice creation calls pricingService.calculateWorkFeeSats() async;
  stores estimatedCostUsd/marginPct/btcPriceUsd on job; after executeWork stores
  actualInputTokens/actualOutputTokens/actualCostUsd; GET response includes
  pricingBreakdown (awaiting_work_payment) and costLedger (complete)
- lib/db/src/schema/jobs.ts: 6 new real/integer columns for cost tracking; schema pushed
- openapi.yaml: PricingBreakdown + CostLedger schemas added to JobStatusResponse
- replit.md: 17 new env vars documented in Cost-based work fee pricing section
2026-03-18 19:20:34 +00:00
alexpaynex
2245be0eaf Update provisioning URL and streamline SSH key delivery
Fixes the hardcoded 'https://' in the stub provisioner's lnbitsUrl to 'http://' and implements an atomic, first-retrieval SSH private key delivery mechanism.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 2f0c982b-02f6-4381-9fc4-34f489842999
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9f85e954-647c-46a5-90a7-396e495a805a/418bf6f8-212b-4bb0-a7a5-8231a061da4e/sPDHkg8
Replit-Helium-Checkpoint-Created: true
2026-03-18 19:10:30 +00:00
alexpaynex
2cab3ef907 Fix review findings #2: template escaping, ops.sh on node, fee NaN guard
1. Escape ${i} bash loop vars in TypeScript template literal (provisioner.ts)
   - Four occurrences: Bitcoin RPC wait, LND REST wait, macaroon wait, LNbits wait
   - Changed ${i}x5s → \${i}x5s so TypeScript doesn't try to resolve 'i'
   - Confirmed: tsc reports no errors in provisioner.ts after fix

2. Install minimal ops.sh on provisioned node via cloud-init (provisioner.ts)
   - Cloud-init step 15 writes /opt/timmy-node/ops.sh with sync/lnd/lnbits/logs cmds
   - Uses single-quoted heredoc (<<'OPSSH') to prevent bash expanding ops.sh's
     own $CMD / ${1:-help} / ${2:-bitcoin} variables during cloud-init execution
   - chmod +x applied after write
   - sync command: docker exec bitcoin bitcoin-cli getblockchaininfo | jq summary
   - lnd, lnbits, logs subcommands also included

3. Update nextSteps to reference installed ops.sh (bootstrap.ts)
   - "Monitor Bitcoin sync (takes 1-2 weeks to reach 100%): bash /opt/timmy-node/ops.sh sync"
   - All other nextSteps reference files/URLs actually present on the node

4. Harden BOOTSTRAP_FEE_SATS parsing against NaN (pricing.ts)
   - parseInt on empty/invalid env var → NaN
   - Added Number.isFinite(rawFee) && rawFee > 0 guard → falls back to 10_000
   - Same pattern could be applied to other numeric env vars as follow-up

End-to-end verified: POST → pay → provisioning → ready with correct nextSteps
2026-03-18 19:04:03 +00:00
alexpaynex
4162ef0edc Fix Task #5 review findings: race guard, full stack cloud-init, volume, node:crypto SSH
4 changes to address code review rejections:

1. Race condition fix (bootstrap.ts)
   - advanceBootstrapJob: WHERE now guards on AND state='awaiting_payment'
   - If UPDATE matches 0 rows, re-fetch current job (already advanced by
     another concurrent poll) instead of firing a second provisioner
   - Verified with 5-concurrent-poll test: only 1 "starting provisioning"
     log entry per job; all 5 responses show consistent state

2. Complete cloud-init to full Bitcoin + LND + LNbits stack (provisioner.ts)
   - Phase 1: packages, Docker, Tailscale, UFW, block volume mount
   - Phase 2: Bitcoin Core started; polls for RPC availability (max 5 min)
   - Phase 3: LND started; waits for REST API (max 6 min)
   - Phase 4: non-interactive LND wallet init via REST:
     POST /v1/genseed → POST /v1/initwallet with base64 password
     (no lncli, no interactive prompts, no expect)
   - Phase 5: waits for admin.macaroon to appear on mounted volume
   - Phase 6: LNbits started with LndRestWallet backend; mounts LND
     data dir so it reads tls.cert + admin.macaroon automatically
   - Phase 7: saves all credentials (RPC pass, LND wallet pass + seed
     mnemonic, LNbits URL) to chmod 600 /root/node-credentials.txt

3. DO block volume support (provisioner.ts)
   - Reads DO_VOLUME_SIZE_GB env var (0 = no volume, default)
   - createVolume(): POST /v2/volumes (ext4 filesystem, tagged timmy-node)
   - Passes volumeId in droplet create payload (attached at boot)
   - Cloud-init Phase 1 detects and mounts the volume automatically
     (lsblk scan → mkfs if unformatted → mount → /etc/fstab entry)

4. SSH keypair via node:crypto (no ssh-keygen) (provisioner.ts)
   - generateKeyPairSync('rsa', { modulusLength: 4096 })
   - Public key: PKCS#1 DER → OpenSSH wire format via manual DER parser
     (pkcs1DerToSshPublicKey): reads SEQUENCE → n, e INTEGERs → ssh-rsa
     base64 string with proper mpint encoding (leading 0x00 for high bit)
   - Private key: PKCS#1 PEM (-----BEGIN RSA PRIVATE KEY-----)
   - Both stub and real paths use the same generateSshKeypair() function
   - Removes runtime dependency on host ssh-keygen binary entirely
2026-03-18 18:58:40 +00:00
alexpaynex
a3acb4a0c6 Fix Task #5 review findings: race guard, full stack cloud-init, volume, node:crypto SSH
4 changes to address code review rejections:

1. Race condition fix (bootstrap.ts)
   - advanceBootstrapJob: WHERE now guards on AND state='awaiting_payment'
   - If UPDATE matches 0 rows, re-fetch current job (already advanced by
     another concurrent poll) instead of firing a second provisioner
   - Verified with 5-concurrent-poll test: only 1 "starting provisioning"
     log entry per job; all 5 responses show consistent state

2. Complete cloud-init to full Bitcoin + LND + LNbits stack (provisioner.ts)
   - Phase 1: packages, Docker, Tailscale, UFW, block volume mount
   - Phase 2: Bitcoin Core started; polls for RPC availability (max 5 min)
   - Phase 3: LND started; waits for REST API (max 6 min)
   - Phase 4: non-interactive LND wallet init via REST:
     POST /v1/genseed → POST /v1/initwallet with base64 password
     (no lncli, no interactive prompts, no expect)
   - Phase 5: waits for admin.macaroon to appear on mounted volume
   - Phase 6: LNbits started with LndRestWallet backend; mounts LND
     data dir so it reads tls.cert + admin.macaroon automatically
   - Phase 7: saves all credentials (RPC pass, LND wallet pass + seed
     mnemonic, LNbits URL) to chmod 600 /root/node-credentials.txt

3. DO block volume support (provisioner.ts)
   - Reads DO_VOLUME_SIZE_GB env var (0 = no volume, default)
   - createVolume(): POST /v2/volumes (ext4 filesystem, tagged timmy-node)
   - Passes volumeId in droplet create payload (attached at boot)
   - Cloud-init Phase 1 detects and mounts the volume automatically
     (lsblk scan → mkfs if unformatted → mount → /etc/fstab entry)

4. SSH keypair via node:crypto (no ssh-keygen) (provisioner.ts)
   - generateKeyPairSync('rsa', { modulusLength: 4096 })
   - Public key: PKCS#1 DER → OpenSSH wire format via manual DER parser
     (pkcs1DerToSshPublicKey): reads SEQUENCE → n, e INTEGERs → ssh-rsa
     base64 string with proper mpint encoding (leading 0x00 for high bit)
   - Private key: PKCS#1 PEM (-----BEGIN RSA PRIVATE KEY-----)
   - Both stub and real paths use the same generateSshKeypair() function
   - Removes runtime dependency on host ssh-keygen binary entirely
2026-03-18 18:55:40 +00:00
alexpaynex
f43e782c50 Task #5: Lightning-gated node bootstrap (proof-of-concept)
Pay a Lightning invoice → Timmy auto-provisions a Bitcoin full node on DO.

New: lib/db/src/schema/bootstrap-jobs.ts
- bootstrap_jobs table: id, state, amountSats, paymentHash, paymentRequest,
  dropletId, nodeIp, tailscaleHostname, lnbitsUrl, sshPrivateKey,
  sshKeyDelivered (bool), errorMessage, createdAt, updatedAt
- States: awaiting_payment | provisioning | ready | failed
- Payment data stored inline (no FK to jobs/invoices tables — separate entity)
- db:push applied to create table in Postgres

New: artifacts/api-server/src/lib/provisioner.ts
- ProvisionerService: stubs when DO_API_TOKEN absent, real otherwise
- Stub mode: generates a real RSA 4096-bit SSH keypair via ssh-keygen,
  returns RFC 5737 test IP + fake Tailscale hostname after 2s delay
- Real mode: upload SSH public key to DO → generate Tailscale auth key →
  create DO droplet with cloud-init user_data → poll for public IP (2 min)
- buildCloudInitScript(): non-interactive bash that installs Docker + Tailscale
  + UFW + Bitcoin Knots via docker-compose; joins Tailscale if authkey provided
- provision() designed as fire-and-forget (void); updates DB to ready/failed

New: artifacts/api-server/src/routes/bootstrap.ts
- POST /api/bootstrap: create job + LNbits invoice, return paymentRequest
- GET /api/bootstrap/🆔 poll-driven state machine
  * awaiting_payment: checks payment, fires provisioner on confirm
  * provisioning: returns progress message
  * ready: delivers credentials; SSH private key delivered once then cleared
  * failed: returns error message
- Stub mode message includes the exact /dev/stub/pay URL for easy testing
- nextSteps array guides user through post-provision setup

Updated: artifacts/api-server/src/lib/pricing.ts
- Added bootstrapFee field reading BOOTSTRAP_FEE_SATS env var (default 10000)
- calculateBootstrapFeeSats() method

Updated: artifacts/api-server/src/routes/index.ts
- Mounts bootstrapRouter

Updated: replit.md
- Documents all 7 new env vars (DO_API_TOKEN, DO_REGION, DO_SIZE, etc.)
- Full curl-based flow example with annotated response shape

End-to-end verified in stub mode: POST → pay → provisioning → ready (SSH key)
→ second GET clears key and shows sshKeyNote
2026-03-18 18:47:48 +00:00
alexpaynex
0921fa1ca3 Make the demo user interface accessible through the API
Add a new UI route to serve the interactive demo interface at `/api/ui`.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 3fb69144-fc09-46cf-8560-9b4bc828c60f
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9f85e954-647c-46a5-90a7-396e495a805a/418bf6f8-212b-4bb0-a7a5-8231a061da4e/sPDHkg8
Replit-Helium-Checkpoint-Created: true
2026-03-18 18:06:44 +00:00
alexpaynex
fc4fd50e33 Add automated testing flow to reduce manual effort
Integrate a new testkit endpoint and update package.json scripts to enable automated testing via `pnpm test` and `pnpm test:prod`, including a new test case for request body size limits.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 60472e18-59b7-4877-a9a2-16381573ab68
Replit-Helium-Checkpoint-Created: true
2026-03-18 17:43:01 +00:00
alexpaynex
f5811da508 Improve input validation and error messaging for user requests
Update API endpoints for jobs and demo routes to enforce a maximum character limit of 500 for the 'request' field. Refine error messages to distinguish between missing input and input exceeding the character limit.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 8c43b6a7-30d3-4806-8d46-3d364b17c284
Replit-Helium-Checkpoint-Created: true
2026-03-18 17:33:21 +00:00
alexpaynex
53bc93a9b4 Add automated testing script and expose payment hashes
Integrates a new bash script for automated end-to-end testing of the Timmy API. Updates API routes to expose payment hashes in stub mode for easier invoice payment simulation during testing. Modifies test plan documentation to include the new automated script.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 6f2776b0-a913-41d3-a988-759a82feb6f3
Replit-Helium-Checkpoint-Created: true
2026-03-18 17:30:13 +00:00
alexpaynex
e1bc20b03c Add more dependencies to the API server build process
Add '@anthropic-ai/sdk', 'p-limit', and 'p-retry' to the build allowlist for the API server.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: a82c4f30-2f20-4eb4-a793-c6f68c6d9413
Replit-Helium-Checkpoint-Created: true
2026-03-18 17:12:00 +00:00
alexpaynex
f3de9e9ab0 Add trust proxy configuration and job ID validation
Adds `app.set('trust proxy', 1)` to `app.ts` for correct IP rate limiting and implements Zod validation for the `:id` parameter in the `GET /jobs/:id` route within `jobs.ts`.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 7049b42e-1d56-48f8-bf54-25cef7c7880b
Replit-Helium-Checkpoint-Created: true
2026-03-18 15:34:05 +00:00
alexpaynex
4e8adbcb93 Task #3: MVP API — payment-gated jobs + demo endpoint
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
2026-03-18 15:31:26 +00:00
alexpaynex
44f7e24b45 Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables in lib/db/src/schema/
- schema barrel exports jobs, invoices, conversations, messages
- Schema pushed successfully

LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class: constructor accepts optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials absent; logs clear warning
- createInvoice(amountSats, memo) -> { paymentHash, paymentRequest }
- checkInvoicePaid(paymentHash) -> boolean
- stubMarkPaid(hash) helper for dev flows (guarded to stub mode only)
- Real LNbits REST v1 calls wired behind stub guard

AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel/workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
  uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- No @anthropic-ai/sdk import; types inferred from SDK response union
- Wired via Replit Anthropic AI Integration

PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Fully deterministic, no LLM

Smoke test (artifacts/api-server/src/smoke.ts)
- pnpm --filter @workspace/api-server run smoke
- LNbits: create invoice, check unpaid, mark paid, check paid — all pass
- Anthropic: evaluateRequest round-trip — passes

replit.md: documented LNBITS_URL, LNBITS_API_KEY and auto-provisioned secrets
2026-03-18 15:18:23 +00:00
alexpaynex
fbc9bbc046 Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully

LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard

AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
  uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)

PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic

Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed

replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
alexpaynex
e163a5d0fe Task #2: MVP Foundation — DB schema, LNbits stub, Anthropic agent
- Added jobs and invoices Drizzle schemas (lib/db/src/schema/)
- Updated DB schema barrel to export all four tables (jobs, invoices, conversations, messages)
- Applied schema to PostgreSQL via drizzle-kit push
- Set up Anthropic AI integration (claude-haiku-4-5 for eval, claude-sonnet-4-6 for work)
- Copied integrations-anthropic-ai template into lib/
- Added @workspace/integrations-anthropic-ai dep to api-server and tsconfig references
- Created pricing.ts: eval fee = 10 sats fixed, work fee = 50/100/250 sats by request length
- Created agent.ts: evaluateRequest (Haiku, JSON structured output) + executeRequest (Sonnet)
- Created lnbits.ts: stubbed payment layer (in-memory Set, markInvoicePaid for testing)
  - Real LNbits swap-in requires only LNBITS_URL + LNBITS_API_KEY env vars
2026-03-18 15:09:48 +00:00
alexpaynex
b095efcfd3 Add AI agent capabilities and integrate with Anthropic and LNbits
Integrate Anthropic AI for agent capabilities, introduce database schemas for jobs and invoices, and set up LNbits for payment processing.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: cce28acc-aeac-46ff-80ec-af4ade39e30f
Replit-Helium-Checkpoint-Created: true
2026-03-18 14:59:02 +00:00
agent
c8ed262197 Initial commit 2026-03-13 23:21:55 +00:00