ray-finance 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +38 -11
  2. package/dist/ai/agent.js +16 -3
  3. package/dist/ai/context.js +6 -2
  4. package/dist/ai/insights.js +26 -3
  5. package/dist/ai/redactor.js +11 -0
  6. package/dist/ai/system-prompt.js +2 -2
  7. package/dist/ai/tools.js +4 -0
  8. package/dist/cli/backup.js +18 -9
  9. package/dist/cli/chat.js +146 -40
  10. package/dist/cli/format.d.ts +2 -0
  11. package/dist/cli/format.js +25 -0
  12. package/dist/cli/index.js +12 -2
  13. package/dist/cli/setup.js +7 -1
  14. package/dist/daily-sync.js +19 -4
  15. package/dist/db/connection.js +9 -1
  16. package/dist/db/encryption.js +18 -7
  17. package/dist/db/schema.js +6 -1
  18. package/dist/public/link.html +47 -24
  19. package/dist/public/ray-logo-dark.png +0 -0
  20. package/dist/queries/index.js +8 -8
  21. package/dist/server.js +33 -1
  22. package/package.json +7 -2
  23. package/.claude/settings.local.json +0 -16
  24. package/.env.example +0 -13
  25. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -19
  26. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -9
  27. package/.github/PULL_REQUEST_TEMPLATE.md +0 -5
  28. package/.github/workflows/ci.yml +0 -21
  29. package/CHANGELOG.md +0 -16
  30. package/CODE_OF_CONDUCT.md +0 -31
  31. package/CONTRIBUTING.md +0 -41
  32. package/Dockerfile +0 -8
  33. package/SECURITY.md +0 -36
  34. package/SPEC.md +0 -374
  35. package/docker-compose.yml +0 -9
  36. package/site/next-env.d.ts +0 -6
  37. package/site/next.config.ts +0 -7
  38. package/site/package-lock.json +0 -1661
  39. package/site/package.json +0 -24
  40. package/site/postcss.config.mjs +0 -7
  41. package/site/public/ray-og.jpg +0 -0
  42. package/site/public/robots.txt +0 -4
  43. package/site/public/sitemap.xml +0 -8
  44. package/site/src/app/copy-command.tsx +0 -31
  45. package/site/src/app/globals.css +0 -87
  46. package/site/src/app/layout.tsx +0 -64
  47. package/site/src/app/page.tsx +0 -841
  48. package/site/src/app/pii-scramble.tsx +0 -190
  49. package/site/src/app/reveal.tsx +0 -29
  50. package/site/tsconfig.json +0 -21
  51. package/src/ai/agent.ts +0 -106
  52. package/src/ai/audit.ts +0 -11
  53. package/src/ai/context.ts +0 -93
  54. package/src/ai/insights.ts +0 -474
  55. package/src/ai/memory.ts +0 -21
  56. package/src/ai/redactor.ts +0 -102
  57. package/src/ai/system-prompt.ts +0 -90
  58. package/src/ai/tools.ts +0 -716
  59. package/src/alerts/index.ts +0 -123
  60. package/src/cli/backup.ts +0 -113
  61. package/src/cli/chat.ts +0 -105
  62. package/src/cli/commands.ts +0 -240
  63. package/src/cli/format.ts +0 -149
  64. package/src/cli/index.ts +0 -193
  65. package/src/cli/scheduler.ts +0 -116
  66. package/src/cli/setup.ts +0 -189
  67. package/src/config.ts +0 -81
  68. package/src/daily-sync.ts +0 -155
  69. package/src/db/connection.ts +0 -38
  70. package/src/db/encryption.ts +0 -29
  71. package/src/db/helpers.ts +0 -47
  72. package/src/db/schema.ts +0 -196
  73. package/src/index.ts +0 -3
  74. package/src/plaid/client.ts +0 -25
  75. package/src/plaid/link.ts +0 -25
  76. package/src/plaid/sync.ts +0 -219
  77. package/src/public/link.html +0 -161
  78. package/src/queries/index.ts +0 -586
  79. package/src/scoring/index.ts +0 -468
  80. package/src/server.ts +0 -162
  81. package/tsconfig.json +0 -16
  82. /package/{site → dist}/public/favicon.png +0 -0
@@ -1,841 +0,0 @@
1
- import { CopyCommand } from "./copy-command";
2
- import { PIIScramble } from "./pii-scramble";
3
- import { Reveal } from "./reveal";
4
-
5
- const jsonLd = {
6
- "@context": "https://schema.org",
7
- "@type": "SoftwareApplication",
8
- name: "Ray Finance",
9
- description:
10
- "An open-source CLI that connects to your bank and gives you AI-powered financial advice — all running locally on your machine.",
11
- applicationCategory: "FinanceApplication",
12
- operatingSystem: "macOS, Linux, Windows",
13
- url: "https://rayfinance.app",
14
- offers: [
15
- {
16
- "@type": "Offer",
17
- price: "0",
18
- priceCurrency: "USD",
19
- description: "Self-hosted with your own API keys",
20
- },
21
- {
22
- "@type": "Offer",
23
- price: "10",
24
- priceCurrency: "USD",
25
- description: "Ray API Key — managed setup",
26
- },
27
- ],
28
- license: "https://opensource.org/licenses/MIT",
29
- };
30
-
31
- export default function Home() {
32
- return (
33
- <main id="main">
34
- <a
35
- href="#main"
36
- className="sr-only focus:not-sr-only focus:fixed focus:top-4 focus:left-4 focus:z-[60] focus:rounded-lg focus:bg-stone-900 focus:px-4 focus:py-2 focus:text-sm focus:font-medium focus:text-white"
37
- >
38
- Skip to content
39
- </a>
40
- <script
41
- type="application/ld+json"
42
- dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
43
- />
44
- <Nav />
45
- <Hero />
46
- <Terminal />
47
- <TrustBlock />
48
- <Reveal><Story /></Reveal>
49
- <HowItWorks />
50
- <Reveal><Privacy /></Reveal>
51
- <Reveal><Features /></Reveal>
52
- <Reveal><Pricing /></Reveal>
53
- <CTA />
54
- <BuiltBy />
55
- <Footer />
56
- </main>
57
- );
58
- }
59
-
60
- /* ─── Nav ─── */
61
- function Nav() {
62
- return (
63
- <nav aria-label="Main navigation" className="fixed top-0 left-0 right-0 z-50 border-b border-stone-200/60 bg-stone-50/80 backdrop-blur-xl">
64
- <div className="mx-auto flex max-w-6xl items-center justify-between px-6 py-4">
65
- <div className="flex items-center gap-2">
66
- <span className="text-lg font-pixel tracking-tight">ray</span>
67
- <span className="rounded-full bg-lime-400/10 px-2 py-0.5 font-mono text-xs text-lime-500">
68
- v0.2
69
- </span>
70
- </div>
71
- {/* Desktop nav */}
72
- <div className="hidden items-center gap-8 sm:flex">
73
- <a
74
- href="#how-it-works"
75
- className="rounded-lg px-3 py-2 text-sm text-stone-500 transition-colors hover:text-stone-900"
76
- >
77
- How it works
78
- </a>
79
- <a
80
- href="#privacy"
81
- className="rounded-lg px-3 py-2 text-sm text-stone-500 transition-colors hover:text-stone-900"
82
- >
83
- Privacy
84
- </a>
85
- <a
86
- href="#pricing"
87
- className="rounded-lg px-3 py-2 text-sm text-stone-500 transition-colors hover:text-stone-900"
88
- >
89
- Pricing
90
- </a>
91
- <a
92
- href="https://github.com/cdinnison/ray-finance"
93
- className="inline-flex items-center gap-2 rounded-full bg-stone-900 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-stone-800"
94
- >
95
- <GitHubIcon />
96
- View on GitHub
97
- </a>
98
- </div>
99
- {/* Mobile nav */}
100
- <div className="flex items-center gap-3 sm:hidden">
101
- <a href="#how-it-works" className="rounded-full px-2 py-2 text-xs text-stone-500">How</a>
102
- <a href="#pricing" className="rounded-full px-2 py-2 text-xs text-stone-500">Pricing</a>
103
- <a
104
- href="https://github.com/cdinnison/ray-finance"
105
- className="inline-flex items-center gap-2 rounded-full bg-stone-900 px-3 py-2 text-sm font-medium text-white transition-colors hover:bg-stone-800"
106
- >
107
- <GitHubIcon />
108
- GitHub
109
- </a>
110
- </div>
111
- </div>
112
- </nav>
113
- );
114
- }
115
-
116
- /* ─── Hero ─── */
117
- function Hero() {
118
- return (
119
- <section className="relative overflow-hidden pt-32 pb-12">
120
- <div className="mx-auto max-w-5xl px-6 text-center">
121
- <h1 className="animate-fade-up text-3xl leading-[1.1] font-extrabold tracking-tight text-stone-950 sm:text-5xl lg:text-7xl">
122
- An AI financial advisor that runs on your&nbsp;machine
123
- </h1>
124
- <p className="animate-fade-up-delay-1 mx-auto mt-8 max-w-2xl text-lg leading-relaxed text-stone-500 sm:text-xl">
125
- Connect your money accounts, ask anything, and get real answers
126
- from your actual data. Open&nbsp;source. Local&#8209;first.
127
- Two&nbsp;minute set&nbsp;up.
128
- </p>
129
- <div className="animate-fade-up-delay-2 mt-10 flex flex-col items-center gap-5 sm:flex-row sm:justify-center">
130
- <a
131
- href="https://github.com/cdinnison/ray-finance"
132
- className="inline-flex items-center gap-2.5 rounded-full bg-stone-900 px-6 py-3.5 text-sm font-semibold text-white shadow-lg shadow-stone-900/20 transition-colors hover:bg-stone-800"
133
- >
134
- <GitHubIcon />
135
- Get started free
136
- </a>
137
- <CopyCommand
138
- command="npx ray-finance"
139
- className="rounded-full border border-stone-200 bg-white px-5 py-3 text-sm text-stone-700 shadow-sm transition-colors hover:border-stone-300"
140
- />
141
- </div>
142
- </div>
143
- </section>
144
- );
145
- }
146
-
147
- /* ─── Terminal Demo ─── */
148
- function Terminal() {
149
- return (
150
- <section className="px-6 py-16">
151
- <div className="mx-auto max-w-3xl">
152
- <div className="overflow-hidden rounded-2xl border border-stone-200 bg-stone-950 shadow-2xl shadow-stone-900/10">
153
- {/* Title bar */}
154
- <div className="flex items-center gap-2 border-b border-stone-800 px-4 py-3">
155
- <div className="h-3 w-3 rounded-full bg-stone-700" />
156
- <div className="h-3 w-3 rounded-full bg-stone-700" />
157
- <div className="h-3 w-3 rounded-full bg-stone-700" />
158
- <span className="ml-2 font-mono text-xs text-stone-500">
159
- ray
160
- </span>
161
- </div>
162
- {/* Content */}
163
- <div className="overflow-x-auto p-5 font-mono text-[11px] leading-[1.7] sm:p-8 sm:text-[13px]">
164
- {/* ── Briefing (shown on launch, before user types anything) ── */}
165
- <p className="text-stone-600">Friday, Mar 28</p>
166
- <Blank />
167
- <p className="text-stone-300">
168
- <D>net worth</D>{" "}<W>$45,230</W>{" "}<G>+$120</G>
169
- </p>
170
- <Blank />
171
- <p className="text-stone-300">
172
- <D>spending</D>{" "}<W>$2,340 this month</W>{" "}<D>&middot;</D>{" "}<G>$340 less</G>{" "}<D>vs last month</D>
173
- </p>
174
- <p className="text-stone-300">
175
- {" "}<D>Dining</D>{" "}<G>-$114</G>{" "}<D>&middot;</D>{" "}<D>Shopping</D>{" "}<G>-$142</G>{" "}<D>&middot;</D>{" "}<D>Groceries</D>{" "}<G>-$73</G>
176
- </p>
177
- <Blank />
178
- <p className="text-stone-300 flex items-center gap-2">
179
- {" "}<CSSBar pct={92} color="amber" />{" "}<Y>Dining 92%</Y>
180
- </p>
181
- <Blank />
182
- <p className="text-stone-300 flex items-center gap-2">
183
- {" "}<CSSBar pct={46} color="lime" />{" "}<W>Emergency fund</W>{" "}<D>$18,200/$40,000</D>
184
- </p>
185
- <Blank />
186
- <p className="text-stone-300">
187
- <D>upcoming</D>{" "}<D>Netflix $16 in 3d</D>{" "}<D>&middot;</D>{" "}<D>Comcast $142 in 6d</D>
188
- </p>
189
- <Blank />
190
- <p className="text-stone-300">
191
- <D>score</D>{" "}<G>72</G><D>/100</D>{" "}<D>&middot; 5d no dining &middot; 3d on pace</D>
192
- </p>
193
- <Blank />
194
- <div className="border-t border-stone-800 my-3" />
195
- {/* ── Conversation ── */}
196
- <p className="text-stone-300">
197
- <span className="text-stone-600">{"❯ "}</span>
198
- if I quit my job to freelance, how long can I survive?
199
- </p>
200
- <Blank />
201
- <p className="text-stone-300">
202
- Based on your last 3 months: you burn <W>$4,820/mo</W> after
203
- </p>
204
- <p className="text-stone-300">
205
- fixed costs. With <G>$18,200</G> in savings, that&apos;s
206
- </p>
207
- <p className="text-stone-300">
208
- <W>3.8 months</W> of runway at current spend.
209
- </p>
210
- <Blank />
211
- <p className="text-stone-300">
212
- Cut dining and shopping to last-month levels and
213
- </p>
214
- <p className="text-stone-300">
215
- you stretch to <G>5.1 months</G>. Land one $8k contract
216
- </p>
217
- <p className="text-stone-300">
218
- in that window and you never dip below $10k.
219
- </p>
220
- </div>
221
- </div>
222
- </div>
223
- </section>
224
- );
225
- }
226
-
227
- /* ─── Trust Block ─── */
228
- function TrustBlock() {
229
- return (
230
- <section className="py-16">
231
- <div className="mx-auto grid max-w-3xl grid-cols-2 gap-y-6 px-6 sm:flex sm:flex-wrap sm:items-center sm:justify-center sm:gap-x-10 sm:gap-y-4">
232
- <Metric value="Open Source" label="MIT License" />
233
- <span className="hidden text-stone-200 sm:inline">|</span>
234
- <Metric value="AES-256" label="Encrypted database" />
235
- <span className="hidden text-stone-200 sm:inline">|</span>
236
- <Metric value="0" label="Data stored in cloud" />
237
- <span className="hidden text-stone-200 sm:inline">|</span>
238
- <Metric value="5 min" label="Setup time" />
239
- </div>
240
- </section>
241
- );
242
- }
243
-
244
- function Metric({ value, label }: { value: string; label: string }) {
245
- return (
246
- <div className="text-center">
247
- <p className="text-lg font-bold tracking-tight text-stone-900">
248
- {value}
249
- </p>
250
- <p className="text-xs text-stone-400">{label}</p>
251
- </div>
252
- );
253
- }
254
-
255
- /* ─── Story ─── */
256
- function Story() {
257
- return (
258
- <section id="story" className="py-24 sm:py-32">
259
- <div className="mx-auto max-w-3xl px-6">
260
- <h2 className="text-3xl font-extrabold tracking-tight text-stone-950 sm:text-4xl">
261
- You&rsquo;ve tried everything else.
262
- </h2>
263
-
264
- <div className="mt-16 space-y-16">
265
- <StoryBlock
266
- label="The Apps"
267
- title="Dashboards show you what happened."
268
- body="Mint, Copilot, Monarch — they sort your transactions into
269
- pie charts and send you notifications. They're good at showing
270
- you what you spent. They never tell you what to do about it."
271
- />
272
-
273
- <StoryBlock
274
- label="The Spreadsheets"
275
- title="Powerful when you keep them updated."
276
- body="You built the perfect spreadsheet once. Formulas, projections,
277
- a debt payoff timeline. But it only works when you do — and
278
- manual data entry doesn't survive a busy month."
279
- />
280
-
281
- <div className="pl-8">
282
- <p className="font-mono text-sm tracking-wide text-lime-500 uppercase">
283
- Then there&rsquo;s Ray
284
- </p>
285
- <h3 className="mt-3 text-2xl font-bold tracking-tight text-stone-950">
286
- A financial advisor that actually knows your numbers.
287
- </h3>
288
- <p className="mt-4 text-lg leading-relaxed text-stone-500">
289
- Ray connects directly to your bank accounts. It sees every
290
- transaction, every balance, every debt. When you ask &ldquo;can
291
- I afford this?&rdquo; it doesn&rsquo;t guess&nbsp;&mdash; it
292
- queries your actual data, runs the math, and gives you a real
293
- answer. It remembers your goals, tracks your progress, and
294
- proactively flags problems before they become emergencies.
295
- </p>
296
- </div>
297
- </div>
298
- </div>
299
- </section>
300
- );
301
- }
302
-
303
- function StoryBlock({
304
- label,
305
- title,
306
- body,
307
- }: {
308
- label: string;
309
- title: string;
310
- body: string;
311
- }) {
312
- return (
313
- <div className="pl-8">
314
- <p className="font-mono text-sm tracking-wide text-stone-400 uppercase">
315
- {label}
316
- </p>
317
- <h3 className="mt-3 text-2xl font-bold tracking-tight text-stone-950">
318
- {title}
319
- </h3>
320
- <p className="mt-4 text-lg leading-relaxed text-stone-500">{body}</p>
321
- </div>
322
- );
323
- }
324
-
325
- /* ─── How It Works ─── */
326
- function HowItWorks() {
327
- return (
328
- <section id="how-it-works" className="bg-stone-950 py-24 sm:py-32">
329
- <div className="mx-auto max-w-5xl px-6">
330
- <p className="font-mono text-sm tracking-wide text-lime-400 uppercase">
331
- How it works
332
- </p>
333
- <h2 className="mt-4 text-3xl font-extrabold tracking-tight text-white sm:text-4xl">
334
- Three commands. Five minutes.
335
- </h2>
336
-
337
- <div className="mt-16 grid gap-12 sm:grid-cols-2">
338
- {/* Quick Setup */}
339
- <div className="rounded-2xl border border-stone-800 bg-stone-900/50 p-8">
340
- <div className="mb-8 flex items-center gap-3">
341
- <span className="rounded-full bg-lime-400/10 px-3 py-1 font-mono text-xs font-medium text-lime-400">
342
- most popular
343
- </span>
344
- <h3 className="text-lg font-bold text-white">Quick Setup</h3>
345
- </div>
346
- <div className="space-y-8">
347
- <Step
348
- num="01"
349
- title="Install"
350
- code="npx ray-finance"
351
- description="One command. No dependencies to manage."
352
- />
353
- <Step
354
- num="02"
355
- title="Subscribe in the CLI"
356
- code="ray setup"
357
- description="Setup opens Stripe checkout in your browser. Paste the key back into the terminal."
358
- />
359
- <Step
360
- num="03"
361
- title="Connect & chat"
362
- code="ray link → ray"
363
- description="Link your accounts, then start asking questions. Ray handles the rest."
364
- />
365
- </div>
366
- </div>
367
-
368
- {/* Self-Hosted */}
369
- <div className="rounded-2xl border border-stone-800/50 p-8">
370
- <div className="mb-8">
371
- <h3 className="text-lg font-bold text-white">Self-Hosted</h3>
372
- </div>
373
- <div className="space-y-8">
374
- <Step
375
- num="01"
376
- title="Get your API keys"
377
- code="anthropic.com + plaid.com"
378
- description="Bring your own Anthropic API key and Plaid production credentials."
379
- />
380
- <Step
381
- num="02"
382
- title="Install & configure"
383
- code="npx ray-finance"
384
- description="Enter each key during setup. Full control over which models and environments you use."
385
- />
386
- <Step
387
- num="03"
388
- title="Connect & chat"
389
- code="ray link → ray"
390
- description="Same experience, your own infrastructure. No third-party proxy."
391
- />
392
- </div>
393
- </div>
394
- </div>
395
- </div>
396
- </section>
397
- );
398
- }
399
-
400
- function Step({
401
- num,
402
- title,
403
- code,
404
- description,
405
- }: {
406
- num: string;
407
- title: string;
408
- code: string;
409
- description: string;
410
- }) {
411
- return (
412
- <div>
413
- <span className="font-pixel text-sm text-stone-600">{num}</span>
414
- <h3 className="mt-3 text-base font-bold text-white">{title}</h3>
415
- <code className="mt-3 block whitespace-pre rounded-lg bg-stone-900 px-4 py-3 font-mono text-sm text-lime-400">
416
- {code}
417
- </code>
418
- <p className="mt-3 text-sm leading-relaxed text-stone-400">
419
- {description}
420
- </p>
421
- </div>
422
- );
423
- }
424
-
425
- /* ─── Privacy ─── */
426
- function Privacy() {
427
- return (
428
- <section id="privacy" className="py-24 sm:py-32">
429
- <div className="mx-auto max-w-5xl px-6">
430
- <div className="max-w-2xl">
431
- <p className="font-mono text-sm tracking-wide text-lime-500 uppercase">
432
- Privacy
433
- </p>
434
- <h2 className="mt-4 text-3xl font-extrabold tracking-tight text-stone-950 sm:text-4xl">
435
- Your financial data is never stored outside your machine.
436
- </h2>
437
- <p className="mt-6 text-lg leading-relaxed text-stone-500">
438
- Ray runs entirely on your computer. There&rsquo;s no cloud, no
439
- account, no server storing your data. Your financial history lives
440
- in an encrypted database on your hard drive, and your name is
441
- scrubbed before anything reaches the AI.
442
- </p>
443
- </div>
444
-
445
- <div className="mt-16">
446
- <PIIScramble />
447
- </div>
448
-
449
- <div className="mt-12 grid gap-8 sm:grid-cols-2 lg:grid-cols-4">
450
- <PrivacyCard
451
- title="Encrypted at rest"
452
- description="AES-256 encrypted database with scrypt key derivation. File permissions locked to your user account."
453
- href="https://github.com/cdinnison/ray-finance/blob/main/src/db/schema.ts"
454
- />
455
- <PrivacyCard
456
- title="No cloud storage"
457
- description="Everything stays in ~/.ray on your machine. Even with a Ray API key, data is processed in-flight and never stored on our servers."
458
- />
459
- <PrivacyCard
460
- title="Fully auditable"
461
- description="Every AI tool call is logged locally. You can see exactly what data was accessed and when."
462
- href="https://github.com/cdinnison/ray-finance/blob/main/src/ai/agent.ts"
463
- />
464
- <PrivacyCard
465
- title="Three outbound calls"
466
- description="Two outbound calls: Plaid for bank sync, Anthropic for AI chat (PII-masked). No telemetry. No analytics."
467
- href="https://github.com/cdinnison/ray-finance/blob/main/src/plaid/client.ts"
468
- />
469
- </div>
470
- </div>
471
- </section>
472
- );
473
- }
474
-
475
- function PrivacyCard({
476
- title,
477
- description,
478
- href,
479
- }: {
480
- title: string;
481
- description: string;
482
- href?: string;
483
- }) {
484
- return (
485
- <div className="rounded-xl border border-stone-200 bg-white p-6">
486
- <h3 className="text-base font-semibold text-stone-900">{title}</h3>
487
- <p className="mt-2 text-sm leading-relaxed text-stone-500">
488
- {description}
489
- </p>
490
- {href && (
491
- <a
492
- href={href}
493
- className="mt-3 inline-block py-2 font-mono text-xs text-stone-400 underline decoration-stone-300 underline-offset-4 transition-colors hover:text-stone-600"
494
- >
495
- view source
496
- </a>
497
- )}
498
- </div>
499
- );
500
- }
501
-
502
- /* ─── Features ─── */
503
- function Features() {
504
- return (
505
- <section id="features" className="border-t border-stone-200 bg-stone-100/50 py-24 sm:py-32">
506
- <div className="mx-auto max-w-5xl px-6">
507
- <p className="font-mono text-sm tracking-wide text-lime-500 uppercase">
508
- What Ray can do
509
- </p>
510
- <h2 className="mt-4 text-3xl font-extrabold tracking-tight text-stone-950 sm:text-4xl">
511
- Ask a question. Get an answer from your actual numbers.
512
- </h2>
513
- <p className="mt-4 max-w-2xl text-lg text-stone-500">
514
- Ray has 30+ tools that query your real financial data. It looks
515
- things up, runs calculations, and takes action.
516
- </p>
517
-
518
- <div className="mt-16 grid gap-x-12 gap-y-10 sm:grid-cols-2 lg:grid-cols-3">
519
- <Feature
520
- question={`"Where is all my money going?"`}
521
- description="Category breakdowns, period comparisons, and trend detection. Ray finds the patterns you miss in your own spending."
522
- />
523
- <Feature
524
- question={`"Should I pay off debt or invest?"`}
525
- description="Ray simulates avalanche, snowball, and custom payoff strategies against your real balances and interest rates. Actual math, not rules of thumb."
526
- />
527
- <Feature
528
- question={`"Am I on track this month?"`}
529
- description="Budget pacing that warns you before you overspend, not after. Set limits in conversation and Ray tracks against real transactions."
530
- />
531
- <Feature
532
- question={`"Can I afford to take this trip?"`}
533
- description="Ray projects your balance forward based on actual income and spending patterns. See the impact before you commit."
534
- />
535
- <Feature
536
- question={`"How are my investments doing?"`}
537
- description="Holdings, cost basis, gains and losses pulled directly from your brokerage. One question replaces logging into three apps."
538
- />
539
- <Feature
540
- question={`"How's my score today?"`}
541
- description="A daily 0-100 behavior score with streaks and unlockable achievements. No restaurants for a week? That's Kitchen Hero. Five zero-spend days? Monk Mode. It turns financial discipline into a game you actually want to play."
542
- />
543
- <Feature
544
- question={`"What did we decide last time?"`}
545
- description="Ray remembers your goals, preferences, life events, and past decisions. Every conversation builds on the last one."
546
- />
547
- </div>
548
- </div>
549
- </section>
550
- );
551
- }
552
-
553
- function Feature({
554
- question,
555
- description,
556
- }: {
557
- question: string;
558
- description: string;
559
- }) {
560
- return (
561
- <div>
562
- <h3 className="font-mono text-base font-medium text-stone-900">
563
- {question}
564
- </h3>
565
- <p className="mt-2 text-sm leading-relaxed text-stone-500">
566
- {description}
567
- </p>
568
- </div>
569
- );
570
- }
571
-
572
- /* ─── Pricing ─── */
573
- function Pricing() {
574
- return (
575
- <section id="pricing" className="py-24 sm:py-32">
576
- <div className="mx-auto max-w-5xl px-6">
577
- <div className="text-center">
578
- <p className="font-mono text-sm tracking-wide text-lime-500 uppercase">
579
- Pricing
580
- </p>
581
- <h2 className="mt-4 text-3xl font-extrabold tracking-tight text-stone-950 sm:text-4xl">
582
- Free forever. Or skip the setup.
583
- </h2>
584
- </div>
585
-
586
- <div className="mx-auto mt-16 grid max-w-4xl gap-8 sm:grid-cols-2">
587
- {/* Self-Hosted */}
588
- <div className="rounded-2xl border-2 border-lime-400 bg-white p-8">
589
- <div className="flex items-center gap-3">
590
- <h3 className="text-lg font-bold text-stone-900">Self-Hosted</h3>
591
- <span className="rounded-full bg-lime-400/10 px-2.5 py-0.5 text-xs font-medium text-lime-600">
592
- full control
593
- </span>
594
- </div>
595
- <p className="mt-1 text-sm text-stone-500">Bring your own keys</p>
596
- <p className="mt-6">
597
- <span className="text-4xl font-extrabold tracking-tight text-stone-900">
598
- $0
599
- </span>
600
- <span className="text-sm text-stone-500">/forever</span>
601
- </p>
602
- <ul className="mt-8 space-y-3 text-sm text-stone-600">
603
- <PricingItem>Open source, MIT licensed</PricingItem>
604
- <PricingItem>Your own Anthropic API key</PricingItem>
605
- <PricingItem>Your own Plaid credentials</PricingItem>
606
- <PricingItem>Full model selection</PricingItem>
607
- <PricingItem>All features included</PricingItem>
608
- </ul>
609
- <a
610
- href="https://github.com/cdinnison/ray-finance"
611
- className="mt-8 block rounded-full bg-stone-900 py-3 text-center text-sm font-semibold text-white transition-colors hover:bg-stone-800"
612
- >
613
- View on GitHub
614
- </a>
615
- </div>
616
-
617
- {/* Ray API Key */}
618
- <div className="rounded-2xl border border-stone-200 bg-white p-8">
619
- <div className="flex items-center gap-3">
620
- <h3 className="text-lg font-bold text-stone-900">
621
- Ray Hosted Keys
622
- </h3>
623
- <span className="rounded-full bg-stone-100 px-2.5 py-0.5 text-xs font-medium text-stone-500">
624
- skip the setup
625
- </span>
626
- </div>
627
- <p className="mt-1 text-sm text-stone-500">
628
- We handle everything
629
- </p>
630
- <p className="mt-6">
631
- <span className="text-4xl font-extrabold tracking-tight text-stone-900">
632
- $10
633
- </span>
634
- <span className="text-sm text-stone-500">/month</span>
635
- </p>
636
- <ul className="mt-8 space-y-3 text-sm text-stone-600">
637
- <PricingItem>AI and bank access included</PricingItem>
638
- <PricingItem>No Plaid application needed</PricingItem>
639
- <PricingItem>Ready in 2 minutes</PricingItem>
640
- <PricingItem>Same privacy guarantees</PricingItem>
641
- <PricingItem>All features included</PricingItem>
642
- </ul>
643
- <CopyCommand
644
- command="npx ray-finance"
645
- className="mt-8 block rounded-full bg-stone-50 px-4 py-3 text-center text-sm text-stone-600 transition-colors hover:bg-stone-100"
646
- />
647
- </div>
648
- </div>
649
- </div>
650
- </section>
651
- );
652
- }
653
-
654
- function PricingItem({ children }: { children: React.ReactNode }) {
655
- return (
656
- <li className="flex items-start gap-2">
657
- <svg
658
- className="mt-0.5 h-4 w-4 shrink-0 text-lime-500"
659
- fill="none"
660
- viewBox="0 0 24 24"
661
- strokeWidth={2.5}
662
- stroke="currentColor"
663
- aria-hidden="true"
664
- >
665
- <path
666
- strokeLinecap="round"
667
- strokeLinejoin="round"
668
- d="M4.5 12.75l6 6 9-13.5"
669
- />
670
- </svg>
671
- {children}
672
- </li>
673
- );
674
- }
675
-
676
- /* ─── CTA ─── */
677
- function CTA() {
678
- return (
679
- <section className="bg-stone-950 py-24 sm:py-32">
680
- <div className="mx-auto max-w-3xl px-6 text-center">
681
- <h2 className="text-2xl leading-[1.3] font-extrabold tracking-tight text-white sm:text-3xl lg:text-5xl">
682
- You&rsquo;re already making financial decisions without the full&nbsp;picture.
683
- </h2>
684
- <p className="mx-auto mt-6 max-w-xl text-lg text-stone-400">
685
- Ray is free, open source, and takes five minutes to set up.
686
- Use a Ray API key for instant setup, or bring your own keys.
687
- </p>
688
- <div className="mt-10 flex flex-col items-center gap-6">
689
- <CopyCommand
690
- command="npx ray-finance"
691
- className="rounded-full border border-stone-800 bg-stone-900 px-6 py-3.5 text-sm text-lime-400 [&>span:first-child]:text-stone-500"
692
- />
693
- <div className="flex items-center gap-6">
694
- <a
695
- href="https://github.com/cdinnison/ray-finance"
696
- className="inline-flex items-center gap-2 rounded-full bg-white px-5 py-2.5 text-sm font-semibold text-stone-900 transition-colors hover:bg-stone-100"
697
- >
698
- <GitHubIcon />
699
- Star on GitHub
700
- </a>
701
- <a
702
- href="https://github.com/cdinnison/ray-finance#readme"
703
- className="text-sm font-medium text-stone-400 underline decoration-stone-700 underline-offset-4 transition-colors hover:text-white"
704
- >
705
- Read the docs
706
- </a>
707
- </div>
708
- </div>
709
- </div>
710
- </section>
711
- );
712
- }
713
-
714
- /* ─── Built By ─── */
715
- function BuiltBy() {
716
- return (
717
- <section className="py-24 sm:py-32">
718
- <div className="mx-auto max-w-2xl px-6 text-center">
719
- <p className="font-mono text-sm tracking-wide text-stone-400 uppercase">
720
- Why I built this
721
- </p>
722
- <p className="mt-6 text-lg leading-relaxed text-stone-500">
723
- I tried every finance app, built every spreadsheet, and talked to
724
- a financial advisor who charged $200/hr to tell me things I already
725
- knew. Nothing actually helped me make better decisions with my own
726
- money. So I built the thing I wanted&nbsp;&mdash; an advisor that
727
- knows my real numbers, runs locally, and is honest enough to
728
- open&#8209;source.
729
- </p>
730
- <p className="mt-6 text-sm text-stone-400">
731
- &mdash;{" "}
732
- <a
733
- href="https://github.com/cdinnison"
734
- className="underline decoration-stone-300 underline-offset-4 transition-colors hover:text-stone-600"
735
- >
736
- Clark Dinnison
737
- </a>
738
- </p>
739
- </div>
740
- </section>
741
- );
742
- }
743
-
744
- /* ─── Footer ─── */
745
- function Footer() {
746
- return (
747
- <footer className="border-t border-stone-200 py-8">
748
- <div className="mx-auto max-w-5xl px-6">
749
- <div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
750
- <span className="font-pixel text-sm text-stone-400">ray</span>
751
- <p className="text-sm text-stone-400">
752
- Open source under MIT.
753
- </p>
754
- </div>
755
- <p className="mt-4 max-w-xl text-left text-xs leading-relaxed text-stone-400/70">
756
- Ray is an AI tool, not a licensed financial advisor. Output is
757
- informational, may be inaccurate, and does not constitute financial
758
- advice. Consult a qualified professional before making financial
759
- decisions.
760
- </p>
761
- </div>
762
- </footer>
763
- );
764
- }
765
-
766
- /* ─── Helpers ─── */
767
- function Line({
768
- children,
769
- dim,
770
- prompt,
771
- }: {
772
- children: React.ReactNode;
773
- dim?: boolean;
774
- prompt?: boolean;
775
- }) {
776
- return (
777
- <p className={dim ? "text-stone-500" : "text-stone-300"}>
778
- {prompt && <span className="text-stone-500" aria-hidden="true">{"❯ "}</span>}
779
- {children}
780
- </p>
781
- );
782
- }
783
-
784
- function Blank() {
785
- return <p className="h-5" />;
786
- }
787
-
788
- function G({ children }: { children: React.ReactNode }) {
789
- return <span className="text-lime-400">{children}</span>;
790
- }
791
-
792
- function R({ children }: { children: React.ReactNode }) {
793
- return <span className="text-red-400">{children}</span>;
794
- }
795
-
796
- function Y({ children }: { children: React.ReactNode }) {
797
- return <span className="text-amber-400">{children}</span>;
798
- }
799
-
800
- function W({ children }: { children: React.ReactNode }) {
801
- return <span className="text-white">{children}</span>;
802
- }
803
-
804
- function D({ children }: { children: React.ReactNode }) {
805
- return <span className="text-stone-500">{children}</span>;
806
- }
807
-
808
- function CSSBar({ pct, color }: { pct: number; color: "lime" | "amber" }) {
809
- const total = 8;
810
- const filled = Math.round((Math.min(pct, 100) / 100) * total);
811
- const fillColor = color === "amber" ? "#fbbf24" : "#87da26";
812
- const emptyColor = "#292524";
813
- // Each block is a 6x10 rect with 1px gaps, mimicking terminal block chars
814
- return (
815
- <svg width={total * 7} height={10} className="inline-block align-middle" aria-hidden="true">
816
- {Array.from({ length: total }, (_, i) => (
817
- <rect
818
- key={i}
819
- x={i * 7}
820
- y={0}
821
- width={6}
822
- height={10}
823
- rx={1}
824
- fill={i < filled ? fillColor : emptyColor}
825
- />
826
- ))}
827
- </svg>
828
- );
829
- }
830
-
831
- function GitHubIcon() {
832
- return (
833
- <svg className="h-4 w-4" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
834
- <path
835
- fillRule="evenodd"
836
- d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
837
- clipRule="evenodd"
838
- />
839
- </svg>
840
- );
841
- }