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
This commit is contained in:
alexpaynex
2026-03-18 15:09:48 +00:00
parent b095efcfd3
commit e163a5d0fe

View File

@@ -1,22 +1,4 @@
const LNBITS_URL = process.env.LNBITS_URL;
const LNBITS_API_KEY = process.env.LNBITS_API_KEY;
function getBaseUrl(): string {
if (!LNBITS_URL) {
throw new Error("LNBITS_URL environment variable is not set");
}
return LNBITS_URL.replace(/\/$/, "");
}
function getHeaders(): Record<string, string> {
if (!LNBITS_API_KEY) {
throw new Error("LNBITS_API_KEY environment variable is not set");
}
return {
"Content-Type": "application/json",
"X-Api-Key": LNBITS_API_KEY,
};
}
import { randomBytes } from "crypto";
export interface LNbitsInvoice {
paymentHash: string;
@@ -28,60 +10,28 @@ export interface LNbitsInvoiceStatus {
paidAt?: Date;
}
const paidInvoices = new Set<string>();
export async function createInvoice(
amountSats: number,
memo: string,
): Promise<LNbitsInvoice> {
const baseUrl = getBaseUrl();
const response = await fetch(`${baseUrl}/api/v1/payments`, {
method: "POST",
headers: getHeaders(),
body: JSON.stringify({
out: false,
amount: amountSats,
memo,
}),
});
if (!response.ok) {
const body = await response.text();
throw new Error(`LNbits createInvoice failed (${response.status}): ${body}`);
}
const data = (await response.json()) as {
payment_hash: string;
payment_request: string;
};
return {
paymentHash: data.payment_hash,
paymentRequest: data.payment_request,
};
const paymentHash = randomBytes(32).toString("hex");
const paymentRequest = `lnbcrt${amountSats}u1stub_${paymentHash.slice(0, 16)}`;
console.log(`[stub] Created invoice: ${amountSats} sats — "${memo}" — hash=${paymentHash}`);
return { paymentHash, paymentRequest };
}
export async function checkInvoicePaid(
paymentHash: string,
): Promise<LNbitsInvoiceStatus> {
const baseUrl = getBaseUrl();
const response = await fetch(`${baseUrl}/api/v1/payments/${paymentHash}`, {
method: "GET",
headers: getHeaders(),
});
if (!response.ok) {
const body = await response.text();
throw new Error(`LNbits checkInvoice failed (${response.status}): ${body}`);
if (paidInvoices.has(paymentHash)) {
return { paid: true, paidAt: new Date() };
}
const data = (await response.json()) as {
paid: boolean;
details?: { time?: number };
};
return {
paid: data.paid,
paidAt: data.paid && data.details?.time
? new Date(data.details.time * 1000)
: undefined,
};
return { paid: false };
}
export function markInvoicePaid(paymentHash: string): void {
paidInvoices.add(paymentHash);
console.log(`[stub] Marked invoice paid: hash=${paymentHash}`);
}