diff --git a/Dockerfile b/Dockerfile index 03afcaa..ea74f92 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,11 @@ FROM oven/bun:1-slim WORKDIR /app + +COPY package.json bun.lock ./ +RUN bun install --frozen-lockfile + +COPY . . +RUN bun run build && bun install -g . && bun add -g @earendil-works/pi-coding-agent + +EXPOSE 8008 CMD ["/bin/bash"] diff --git a/docker-compose.yml b/docker-compose.yml index 3f278a5..69543d9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,14 +1,15 @@ services: - bun: + routstrd: build: . - container_name: routstrd-bun - working_dir: /app - volumes: - - .:/app - - bun_home:/root/.bun + container_name: routstrd stdin_open: true tty: true + volumes: + - routstrd_data:/data + ports: + - "8008:8008" + restart: unless-stopped command: /bin/bash volumes: - bun_home: + routstrd_data: diff --git a/src/cli.ts b/src/cli.ts index f1c7472..9a7ddc8 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -231,7 +231,8 @@ program "Mint URL to refund to (defaults to first mint in wallet)", ) .option("-y, --yes", "Skip confirmation prompt", false) - .action(async (options: { mintUrl?: string; yes: boolean }) => { + .option("--xcashu", "Refund xcashu tokens only (uses refundXcashuTokens)", false) + .action(async (options: { mintUrl?: string; yes: boolean; xcashu: boolean }) => { await ensureDaemonRunning(); let mintUrl = options.mintUrl; @@ -257,6 +258,36 @@ program } try { + if (options.xcashu) { + // xcashu path: only refund xcashu tokens + const result = await callDaemon("/refund/xcashu", { + method: "POST", + body: { mintUrl }, + }); + + if (result.error) { + console.log(result.error); + process.exit(1); + } + + const output = result.output as + | { + message: string; + results: Array<{ baseUrl: string; token: string; success: boolean; error?: string }>; + } + | undefined; + + if (output) { + console.log(output.message); + console.log("\nResults:"); + for (const r of output.results) { + const status = r.success ? "success" : `failed: ${r.error || "unknown"}`; + console.log(` - ${r.baseUrl}: ${status}`); + } + } + return; + } + const result = await callDaemon("/refund", { method: "POST", body: { mintUrl }, diff --git a/src/daemon/http/index.ts b/src/daemon/http/index.ts index 68df337..f7f29dd 100644 --- a/src/daemon/http/index.ts +++ b/src/daemon/http/index.ts @@ -537,6 +537,34 @@ export function createDaemonRequestHandler(deps: { return; } + if (req.method === "POST" && url.pathname === "/refund/xcashu") { + try { + const body = await readJsonBody(req); + const mintUrl = getRequiredStringField(body, "mintUrl"); + + const spender = deps.refundClient.getCashuSpender(); + const results = await spender.refundXcashuTokens(mintUrl); + + sendJson(res, 200, { + output: { + message: `Refunded xcashu tokens to ${mintUrl}`, + results: results.map( + (r: { baseUrl: string; token: string; success: boolean; error?: string }) => ({ + baseUrl: r.baseUrl, + token: r.token, + success: r.success, + error: r.error, + }), + ), + }, + }); + } catch (error) { + logger.error(`xcashu refund error: ${toErrorMessage(error)}`); + respondWithError(res, error); + } + return; + } + if (req.method === "GET" && url.pathname === "/balance") { try { const balances = await deps.walletAdapter.getBalances();