diff --git a/README.md b/README.md
index cbb5552..e36c0b4 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,26 @@
# xpenser
-Open-source personal income and expense tracking for people who want a
-self-hosted finance app they can inspect, extend, and connect to their own
-workflows.
+xpenser is an open-source, self-hostable personal finance tracker for people
+who want to replace spreadsheet-based income and expense tracking with
+dashboards, categories, vendors, reports, and API/MCP access.
-
+
+
+It started as a practical replacement for a real Google Spreadsheet accounting
+workflow. The project is early and still evolving, but it is useful enough to
+run, inspect, extend, self-host, or use as a working Cleverbrush Framework
+reference app.
xpenser is also a real-world reference app for
-[Cleverbrush Framework](https://docs.cleverbrush.com). It shows how a
+[Cleverbrush Framework](https://docs.cleverbrush.com), showing how a
schema-first TypeScript stack can drive API contracts, validation, OpenAPI,
typed clients, React forms, auth-aware endpoints, observability, Telegram
workflows, and MCP access from one cohesive application.
## Why xpenser
+- Move spreadsheet-style tracking into a structured app with dashboards,
+ categories, vendors, reports, and searchable transaction history.
- Track income, expenses, refunds, and returns with categories, notes, dates,
vendors, and currencies.
- Review daily, weekly, monthly, quarterly, and yearly summaries with category
@@ -27,6 +34,27 @@ workflows, and MCP access from one cohesive application.
- Connect external tools through API keys, a typed Node client, an MCP
server, and a Telegram bot.
+## Screenshots
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Dashboard month view
+
Transaction history
+
Email reports, API keys, and MCP
+
+
+
## Built With Cleverbrush
xpenser is intentionally small enough to inspect while still exercising
@@ -208,6 +236,25 @@ For public deployments, put your reverse proxy in front of the web app and set
`APP_URL` to the public origin. The API service stays private on the Docker
network and the Next app exposes it under `/external-api`.
+For a smaller public deployment, use `docker-compose.prod.yml` as the starting
+point and provide production secrets for `NEXTAUTH_SECRET`, `AUTH_SECRET`,
+`JWT_SECRET`, `WEB_API_SERVICE_SECRET`, and `TELEGRAM_BOT_SERVICE_SECRET`.
+
+## Optional Integrations
+
+The default `.env.example` keeps external integrations off unless you configure
+their provider credentials:
+
+- OpenAI email insights: set `OPENAI_API_KEY`, `OPENAI_REPORT_MODEL`,
+ `RESEND_API_KEY`, `EMAIL_FROM`, `EMAIL_REPORTS_ENABLED=1`, and
+ `EMAIL_REPORTS_SCHEDULER_ENABLED=1`.
+- Telegram bot workflows: set `TELEGRAM_BOT_TOKEN`, `TELEGRAM_BOT_USERNAME`,
+ and `TELEGRAM_BOT_SERVICE_SECRET`.
+- Vendor enrichment: set `BRANDFETCH_API_KEY` or `BRANDFETCH_CLIENT_ID`, then
+ enable `VENDOR_ENRICHMENT_ENABLED=1`.
+- Google sign-in: configure direct Google OAuth as described above, or leave
+ it disabled and use email/password accounts.
+
## External API
Create an API key from Settings -> Preferences -> API keys. The API key can be
@@ -254,7 +301,8 @@ xpenser exposes an MCP Streamable HTTP endpoint for AI agents at
`/external-api/mcp`. Use the same API key from Settings -> Preferences -> API
keys as a bearer token. MCP tools can read and manage the API-key owner's
vendors, categories, and transactions, so treat MCP access as full account data
-access:
+access. Use a dedicated API key for MCP clients and revoke it when access is no
+longer needed:
```json
{
@@ -309,9 +357,14 @@ product improvements.
## Project Status
-xpenser is early, practical, and evolving. The goal is to remain useful as a
-personal finance app while staying clear enough for developers to learn how a
-Cleverbrush full-stack project fits together.
+xpenser is early, practical, and evolving. It has no meaningful user traction
+yet; feedback on product fit, README clarity, self-hosting, and MCP workflows is
+welcome. The goal is to remain useful as a personal finance app while staying
+clear enough for developers to learn how a Cleverbrush full-stack project fits
+together.
+
+xpenser does not currently ship bank sync, budget planning, net-worth tracking,
+native mobile apps, or mature import pipelines.
## License
diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx
index bc5b7e7..90f58b6 100644
--- a/apps/web/app/layout.tsx
+++ b/apps/web/app/layout.tsx
@@ -6,7 +6,7 @@ import { ThemeProvider } from '@/components/theme-provider';
const publicUrl = process.env.APP_URL ?? 'https://xpenser.cleverbrush.com';
const description =
- 'Open-source personal finance tracking for self-hosted workflows, built with Cleverbrush Framework.';
+ 'Open-source, self-hostable personal finance tracking for replacing spreadsheets with dashboards, reports, API keys, and MCP access.';
export const metadata: Metadata = {
metadataBase: new URL(publicUrl),
@@ -20,7 +20,7 @@ export const metadata: Metadata = {
type: 'website',
url: '/',
siteName: 'xpenser',
- title: 'xpenser',
+ title: 'xpenser - open-source personal finance tracker',
description,
images: [
{
@@ -33,7 +33,7 @@ export const metadata: Metadata = {
},
twitter: {
card: 'summary_large_image',
- title: 'xpenser',
+ title: 'xpenser - open-source personal finance tracker',
description,
images: ['/og-image.png']
}
diff --git a/apps/web/app/manifest.ts b/apps/web/app/manifest.ts
index 376edfc..2c4b8b3 100644
--- a/apps/web/app/manifest.ts
+++ b/apps/web/app/manifest.ts
@@ -5,7 +5,7 @@ export default function manifest(): MetadataRoute.Manifest {
name: 'xpenser',
short_name: 'xpenser',
description:
- 'Open-source personal finance tracking for self-hosted workflows.',
+ 'Open-source, self-hostable personal finance tracking for spreadsheet replacement workflows.',
start_url: '/dashboard',
scope: '/',
display: 'standalone',
diff --git a/apps/web/components/landing-page.test.tsx b/apps/web/components/landing-page.test.tsx
index d415411..04617bd 100644
--- a/apps/web/components/landing-page.test.tsx
+++ b/apps/web/components/landing-page.test.tsx
@@ -15,16 +15,28 @@ describe('LandingPage', () => {
screen.getByRole('heading', { level: 1, name: 'xpenser' })
).toBeTruthy();
expect(
- screen.getByText(/Track income, expenses, refunds/i)
+ screen.getByText(/Replace spreadsheet-based income and expense/i)
+ ).toBeTruthy();
+ expect(screen.getByText(/early and evolving/i)).toBeTruthy();
+ expect(
+ screen.getByAltText(/xpenser dashboard month view/i)
).toBeTruthy();
expect(
screen.getByText(/Learn Cleverbrush from a working app/i)
).toBeTruthy();
expect(screen.getAllByText(/Telegram bot/i).length).toBeGreaterThan(0);
expect(screen.getAllByText(/MCP server/i).length).toBeGreaterThan(0);
- expect(screen.getAllByText(/self-hosted/i).length).toBeGreaterThan(0);
+ expect(screen.getAllByText(/self-host/i).length).toBeGreaterThan(0);
expect(screen.getAllByText(/open-source/i).length).toBeGreaterThan(0);
expect(screen.getAllByText(/MIT licensed/i).length).toBeGreaterThan(0);
+ expect(
+ screen.getAllByText(/Spreadsheet replacement/i).length
+ ).toBeGreaterThan(0);
+ expect(
+ screen.getByText(
+ /read or manage vendors, categories, and transactions/i
+ )
+ ).toBeTruthy();
expect(
screen.getAllByText(/multiple currencies/i).length
).toBeGreaterThan(0);
diff --git a/apps/web/components/landing-page.tsx b/apps/web/components/landing-page.tsx
index 77b4805..850691f 100644
--- a/apps/web/components/landing-page.tsx
+++ b/apps/web/components/landing-page.tsx
@@ -106,28 +106,15 @@ const frameworkFeatures: readonly Feature[] = [
}
];
-const capabilityRows = [
- ['Dashboard', 'Cash flow, net total, category split, trend marks'],
- [
- 'Transactions',
- 'Filtering, editing, nested categories, multi-currency input'
- ],
- ['Conversion', 'Automatic default-currency conversion via Frankfurter'],
- ['Reports', 'Period comparison with charted historical context'],
- [
- 'Email summaries',
- 'Configurable weekly and monthly spending and income insights'
- ],
- ['External API', 'Typed client, API keys, and MCP server']
-] as const;
-
const heroProofs = [
- 'Self-hosted finance app',
+ 'Spreadsheet replacement',
+ 'Self-hostable finance app',
'Multi-currency tracking',
- 'MCP agent access',
+ 'MCP/API access',
'Telegram bot integration',
'Cleverbrush reference code',
- 'MIT licensed'
+ 'MIT licensed',
+ 'Early project'
] as const;
const resourceLinks = [
@@ -178,119 +165,19 @@ function FeatureCard({ description, icon: IconComponent, title }: Feature) {
function ProductPreview() {
return (
-
- Open-source personal finance
+ Early open-source personal finance
xpenser
- Track income, expenses, refunds, currencies,
- vendors, and reports in a self-hosted app that keeps
- the code open for inspection. Under the product
- surface, xpenser shows how Cleverbrush Framework
- connects typed contracts, schema-driven forms,
- observable services, Telegram, API, and MCP
- workflows.
+ Replace spreadsheet-based income and expense
+ tracking with dashboards, categories, vendors,
+ reports, and MCP/API access in a self-hostable app
+ with source you can inspect.
+
+
+ xpenser is early and evolving; feedback on product
+ fit, self-hosting, README clarity, and MCP workflows
+ is welcome.
@@ -506,7 +395,7 @@ export function LandingPage() {
{
icon: BotIcon,
title: 'MCP server',
- text: 'Expose dashboard, category, and transaction data through tool calls.'
+ text: 'Let approved agents read or manage vendors, categories, and transactions through tool calls.'
},
{
icon: SendIcon,
@@ -582,7 +471,7 @@ export function LandingPage() {
xpenser
- Open-source personal finance built with
+ Self-hostable personal finance built with
Cleverbrush Framework.